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:
Casey Romkes 2026-07-09 13:31:53 +02:00 committed by GitHub
commit 027a6de184
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 202 additions and 577 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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`.

View file

@ -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"]

View file

@ -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
>

View file

@ -1,5 +1,5 @@
{
"name": "Adaptive Lighting (CDiT)",
"render_readme": true,
"homeassistant": "2024.12.0"
"homeassistant": "2026.1.0"
}

View file

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-09

View 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

View 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.

View 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`.

View 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)

View file

@ -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

View file

@ -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

View file

@ -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 -

View file

@ -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()

View file

@ -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

View file

@ -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
```