mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-15 08:14:04 +02:00
ci: migrate pytest + Docker to PHCC (retire HA core-checkout harness) (#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.
* 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).
* openspec: mark migrate-ci-to-phcc 4.3 done (CI verified green)
This commit is contained in:
parent
1c6589e067
commit
027a6de184
19 changed files with 202 additions and 577 deletions
50
.github/workflows/docker-build.yml
vendored
50
.github/workflows/docker-build.yml
vendored
|
|
@ -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
|
||||
|
|
@ -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
|
||||
81
.github/workflows/pytest.yaml
vendored
81
.github/workflows/pytest.yaml
vendored
|
|
@ -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
|
||||
|
|
|
|||
59
.github/workflows/update-test-matrix.yaml
vendored
59
.github/workflows/update-test-matrix.yaml
vendored
|
|
@ -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
|
||||
|
|
@ -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`.
|
||||
|
||||
|
|
|
|||
65
Dockerfile
65
Dockerfile
|
|
@ -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"]
|
||||
|
|
@ -86,7 +86,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
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "Adaptive Lighting (CDiT)",
|
||||
"render_readme": true,
|
||||
"homeassistant": "2024.12.0"
|
||||
"homeassistant": "2026.1.0"
|
||||
}
|
||||
|
|
|
|||
2
openspec/changes/migrate-ci-to-phcc/.openspec.yaml
Normal file
2
openspec/changes/migrate-ci-to-phcc/.openspec.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
schema: spec-driven
|
||||
created: 2026-07-09
|
||||
3
openspec/changes/migrate-ci-to-phcc/README.md
Normal file
3
openspec/changes/migrate-ci-to-phcc/README.md
Normal file
|
|
@ -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
|
||||
87
openspec/changes/migrate-ci-to-phcc/design.md
Normal file
87
openspec/changes/migrate-ci-to-phcc/design.md
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
## 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
|
||||
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.
|
||||
- **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.
|
||||
|
||||
## 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).
|
||||
|
||||
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.
|
||||
30
openspec/changes/migrate-ci-to-phcc/proposal.md
Normal file
30
openspec/changes/migrate-ci-to-phcc/proposal.md
Normal file
|
|
@ -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`.
|
||||
30
openspec/changes/migrate-ci-to-phcc/tasks.md
Normal file
30
openspec/changes/migrate-ci-to-phcc/tasks.md
Normal file
|
|
@ -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)
|
||||
- [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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 -
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue