Сделаны упрощения и доработки #31
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| # Теги нужны здесь ради джобы packaging: без них сверка тега с версией | |
| # блюпринта не выполнялась никогда — push тега не подходит под фильтр | |
| # branches, и github.ref_type всегда был `branch`. | |
| tags: ["[0-9]+.[0-9]+.[0-9]+"] | |
| pull_request: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| lint: | |
| name: YAML lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Install yamllint | |
| run: pip install yamllint | |
| - name: Lint YAML sources | |
| run: >- | |
| yamllint -c .yamllint.yaml | |
| blueprints/ .github/ .yamllint.yaml mkdocs.yml | |
| test: | |
| name: Tests (Python ${{ matrix.python-version }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.11", "3.12", "3.13"] | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| cache-dependency-path: requirements-dev.txt | |
| - name: Install development dependencies | |
| run: pip install -r requirements-dev.txt | |
| - name: Run the test suite | |
| run: pytest -v -m "not slow" | |
| mutation: | |
| name: Mutation testing | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| cache-dependency-path: requirements-dev.txt | |
| - name: Install development dependencies | |
| run: pip install -r requirements-dev.txt | |
| # Corrupts one part of the blueprint at a time and asserts the suite | |
| # notices. A green suite that survives a mutation is a vacuous suite. | |
| # | |
| # One full suite run per mutation, so this is the long job: serial it is | |
| # about half an hour. The mutations are independent and each xdist worker | |
| # gets its own sandbox copy, so `-n auto` parallelises them safely. | |
| - name: Verify the suite detects a broken blueprint | |
| run: pytest -v -m slow -n auto | |
| ruff: | |
| name: Python lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| cache-dependency-path: requirements-dev.txt | |
| - name: Install development dependencies | |
| run: pip install -r requirements-dev.txt | |
| - name: Lint the test harness and tools | |
| run: ruff check tests/ tools/ | |
| # tools/ has to keep running on the interpreter people already have. | |
| # The main lint targets 3.11, so on its own it would happily pass code | |
| # that 3.9 cannot even parse. | |
| - name: Lint tools/ against the oldest supported Python | |
| run: ruff check --target-version py39 tools/ | |
| tools-python39: | |
| name: trace_report.py on Python 3.9 | |
| runs-on: ubuntu-latest | |
| # The script is what users run to produce a bug report, so it must work on | |
| # a stock system interpreter with nothing installed. macOS still ships 3.9. | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.9" | |
| - name: Byte-compile without any dependencies | |
| run: python -m py_compile tools/trace_report.py | |
| - name: Run it end to end under an ASCII locale | |
| env: | |
| LC_ALL: C | |
| LANG: C | |
| PYTHONUTF8: "0" | |
| PYTHONCOERCECLOCALE: "0" | |
| run: | | |
| python - <<'PY' | |
| import json, pathlib | |
| folder = pathlib.Path("_ci_traces") | |
| folder.mkdir(exist_ok=True) | |
| (folder / "a.json").write_text(json.dumps({ | |
| "trace": {"trace": {"action/0": [{"changed_variables": {"verdict": "старт"}}]}, | |
| "timestamp": {"start": "2026-08-02T00:05:00+00:00"}}, | |
| "logbookEntries": [{"message": "запускаем зарядку", "when": 1785628800.0}], | |
| }, ensure_ascii=False), encoding="utf-8") | |
| PY | |
| python tools/trace_report.py _ci_traces --vars | |
| python tools/trace_report.py _ci_traces --json > /dev/null | |
| packaging: | |
| name: Release metadata | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| # The version lives in the blueprint description and nowhere else, so it | |
| # can silently disagree with the changelog. A tag disagreeing with either | |
| # is worse: a release note would then describe something other than what | |
| # the tag actually contains. | |
| - name: Version agrees between the blueprint and the changelog | |
| run: | | |
| python - <<'PY' | |
| import pathlib, re, sys | |
| bp = pathlib.Path("blueprints/automation/ev_smart_charging" | |
| "/ev_smart_charging.yaml").read_text(encoding="utf-8") | |
| found = re.search(r"Версия\s+(\d+\.\d+\.\d+)", bp) | |
| if not found: | |
| sys.exit("no version found in the blueprint description") | |
| version = found.group(1) | |
| changelog = pathlib.Path("CHANGELOG.md").read_text(encoding="utf-8") | |
| if f"## [{version}]" not in changelog: | |
| sys.exit(f"blueprint says {version}, CHANGELOG has no such section") | |
| tag = "${{ github.ref_type == 'tag' && github.ref_name || '' }}" | |
| if tag and tag.lstrip("v") != version: | |
| sys.exit(f"tag {tag} does not match blueprint version {version}") | |
| print(f"version {version} agrees across the blueprint and CHANGELOG") | |
| PY | |
| # Home Assistant imports a blueprint into config/blueprints/<domain>/<slug>/, | |
| # and the import URL in the README and the docs is spelled out by hand. | |
| # A moved or renamed file leaves both pointing at a 404, which nobody | |
| # notices until someone tries to install. | |
| - name: Blueprint layout matches the documented import URL | |
| run: | | |
| python - <<'PY' | |
| import pathlib, sys | |
| found = list(pathlib.Path("blueprints").glob("*/*/*.yaml")) | |
| if not found: | |
| sys.exit("no blueprint found under blueprints/<domain>/<slug>/") | |
| docs = pathlib.Path("README.md").read_text(encoding="utf-8") | |
| for path in found: | |
| # Сравниваем полный путь, а не имя файла: переименование каталога | |
| # слага оставляет имя прежним, и проверка по имени пропускала бы | |
| # ровно тот случай, ради которого заведена, — ссылка на импорт | |
| # ведёт на 404, а CI зелёный. | |
| if path.as_posix() not in docs: | |
| sys.exit(f"{path} is not the file the README tells people to import") | |
| print("layout OK;", ", ".join(str(p) for p in found)) | |
| PY | |
| home-assistant-schema: | |
| name: Home Assistant schema (informational) | |
| runs-on: ubuntu-latest | |
| # Home Assistant's internal APIs carry no stability promise, so a failure | |
| # here is a signal to investigate rather than a reason to block a merge. | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.13" | |
| - name: Install Home Assistant | |
| run: pip install homeassistant | |
| # The script exits 77 when it decides to skip; to GitHub that is just a | |
| # failure, so translate it back into a pass. | |
| - name: Validate the blueprint against the real schema | |
| run: | | |
| status=0 | |
| python tests/validate_with_home_assistant.py || status=$? | |
| if [ "$status" -eq 77 ]; then | |
| echo "::notice::schema check skipped" | |
| exit 0 | |
| fi | |
| exit "$status" |