mirror of
https://github.com/home-assistant/core.git
synced 2026-06-30 02:25:31 +02:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60f59862f1 | |||
| affe38913e | |||
| be8139f7be | |||
| c40e066945 | |||
| 30b6730628 |
@@ -11,6 +11,12 @@ core: &core
|
||||
- requirements.txt
|
||||
- setup.cfg
|
||||
|
||||
# Performance benchmark suite (CodSpeed). Only gates the benchmark job; kept out
|
||||
# of the `any` aggregate below so it does not pull in the full test suite.
|
||||
benchmarks: &benchmarks
|
||||
- benchmarks/**
|
||||
- requirements_test.txt
|
||||
|
||||
# Our base platforms, that are used by other integrations
|
||||
base_platforms: &base_platforms
|
||||
- homeassistant/components/ai_task/**
|
||||
|
||||
@@ -14,7 +14,7 @@ env:
|
||||
UV_HTTP_TIMEOUT: 60
|
||||
UV_SYSTEM_PYTHON: "true"
|
||||
# Base image version from https://github.com/home-assistant/docker
|
||||
BASE_IMAGE_VERSION: "2026.07.0"
|
||||
BASE_IMAGE_VERSION: "2026.05.0"
|
||||
ARCHITECTURES: '["amd64", "aarch64"]'
|
||||
|
||||
permissions: {}
|
||||
|
||||
@@ -812,6 +812,55 @@ jobs:
|
||||
python --version
|
||||
mypy --num-workers=4 $(printf "homeassistant/components/%s " ${INTEGRATIONS_GLOB})
|
||||
|
||||
benchmarks:
|
||||
name: Run benchmarks
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # OIDC token CodSpeed mints (no CODSPEED_TOKEN secret)
|
||||
needs:
|
||||
- info
|
||||
- base
|
||||
# Run only when core code or the benchmark suite itself changed. Skipped on
|
||||
# forks, where the OIDC token CodSpeed needs is unavailable. Pushes to dev
|
||||
# that touch core refresh the CodSpeed baseline.
|
||||
if: >-
|
||||
needs.info.outputs.lint_only != 'true'
|
||||
&& (github.event_name != 'pull_request'
|
||||
|| !github.event.pull_request.head.repo.fork)
|
||||
&& (contains(fromJSON(needs.info.outputs.core), 'core')
|
||||
|| contains(fromJSON(needs.info.outputs.core), 'benchmarks'))
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python ${{ fromJson(needs.info.outputs.python_versions)[0] }}
|
||||
id: python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ fromJson(needs.info.outputs.python_versions)[0] }}
|
||||
check-latest: true
|
||||
- name: Restore full Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Run benchmarks
|
||||
uses: CodSpeedHQ/action@a4a36bb07c0638b0b4ca52bf1f3dad1b4289e52f # v4.18.1
|
||||
with:
|
||||
# v4 makes `mode` required; "simulation" is the instrumented
|
||||
# measurement that gives stable, machine-independent results.
|
||||
mode: simulation
|
||||
# No token: auth uses the OIDC id-token minted by the job permissions.
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pytest benchmarks --codspeed --no-cov -o addopts=""
|
||||
|
||||
prepare-pytest-full:
|
||||
name: Split tests for full run
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
@@ -137,7 +137,7 @@ jobs:
|
||||
sed -i "/uv/d" requirements_diff.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@9e17ab1ed5c4c79d8b61e29fa63de25ca2710716 # 2026.07.0
|
||||
uses: home-assistant/wheels@34957438948e0b3dcde73c77750643dadae594f5 # 2026.06.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
@@ -195,7 +195,7 @@ jobs:
|
||||
sed -i "/uv/d" requirements_diff.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@9e17ab1ed5c4c79d8b61e29fa63de25ca2710716 # 2026.07.0
|
||||
uses: home-assistant/wheels@34957438948e0b3dcde73c77750643dadae594f5 # 2026.06.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""CodSpeed performance benchmarks for Home Assistant core hot paths."""
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Shared fixtures for the CodSpeed benchmark suite.
|
||||
|
||||
These benchmarks live outside ``tests`` on purpose: ``testpaths`` only points at
|
||||
``tests``, so the regular suite never collects them. CodSpeed runs them with
|
||||
``pytest benchmarks --codspeed`` and tracks the results per pull request.
|
||||
"""
|
||||
|
||||
from collections.abc import AsyncGenerator, Callable
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from tests.common import async_test_home_assistant
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def hass() -> AsyncGenerator[HomeAssistant]:
|
||||
"""Return a running Home Assistant instance for benchmarking.
|
||||
|
||||
Most hot paths under test (``async_fire``, ``async_set``, ``async_render``)
|
||||
are ``@callback`` methods, so the benchmark fixture can drive them
|
||||
synchronously from within the running loop.
|
||||
"""
|
||||
async with async_test_home_assistant() as hass:
|
||||
yield hass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def populate_states(hass: HomeAssistant) -> Callable[[int], None]:
|
||||
"""Return a helper that fills the state machine with ``count`` sensors.
|
||||
|
||||
Used by the scaling benchmarks to measure a path at several sizes, so an
|
||||
algorithmic regression shows up as the curve bending instead of hiding
|
||||
behind a single constant-factor number.
|
||||
"""
|
||||
|
||||
def _populate(count: int) -> None:
|
||||
for index in range(count):
|
||||
hass.states.async_set(
|
||||
f"sensor.bench_{index}",
|
||||
str(index),
|
||||
{"friendly_name": f"Bench {index}", "unit_of_measurement": "W"},
|
||||
)
|
||||
|
||||
return _populate
|
||||
@@ -0,0 +1,148 @@
|
||||
"""CodSpeed benchmarks for the event bus and event helpers.
|
||||
|
||||
The event bus carries every state change, and ``async_track_state_change_event``
|
||||
is the routing layer almost every automation, template and trigger sits on. A
|
||||
regression in either is felt across the whole system.
|
||||
|
||||
Run locally with: ``pytest benchmarks --codspeed``.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
import pytest
|
||||
from pytest_codspeed import BenchmarkFixture
|
||||
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect,
|
||||
async_dispatcher_send,
|
||||
)
|
||||
from homeassistant.helpers.event import async_call_later, async_track_state_change_event
|
||||
|
||||
|
||||
@callback
|
||||
def _noop(event: Event) -> None:
|
||||
"""Do nothing, cheaply."""
|
||||
|
||||
|
||||
def test_event_fire_no_listeners(
|
||||
benchmark: BenchmarkFixture, hass: HomeAssistant
|
||||
) -> None:
|
||||
"""Fire an event nobody listens to (the bare dispatch cost)."""
|
||||
benchmark(lambda: hass.bus.async_fire("benchmark_event", {"value": 1}))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("listeners", [1, 10])
|
||||
def test_event_fire_callbacks(
|
||||
benchmark: BenchmarkFixture, hass: HomeAssistant, listeners: int
|
||||
) -> None:
|
||||
"""Fire an event with N callback listeners that run inline."""
|
||||
fired = 0
|
||||
|
||||
@callback
|
||||
def listener(event: Event) -> None:
|
||||
nonlocal fired
|
||||
fired += 1
|
||||
|
||||
for _ in range(listeners):
|
||||
hass.bus.async_listen("benchmark_event", listener)
|
||||
|
||||
benchmark(lambda: hass.bus.async_fire("benchmark_event", {"value": 1}))
|
||||
|
||||
assert fired
|
||||
|
||||
|
||||
def test_event_fire_filtered_reject(
|
||||
benchmark: BenchmarkFixture, hass: HomeAssistant
|
||||
) -> None:
|
||||
"""Fire an event whose listener is gated out by an event_filter.
|
||||
|
||||
The filter runs but the listener does not, so this isolates the filter
|
||||
short-circuit cost from the listener body.
|
||||
"""
|
||||
|
||||
@callback
|
||||
def event_filter(event_data: dict) -> bool:
|
||||
return False
|
||||
|
||||
hass.bus.async_listen("benchmark_event", _noop, event_filter=event_filter)
|
||||
|
||||
benchmark(lambda: hass.bus.async_fire("benchmark_event", {"value": 1}))
|
||||
|
||||
|
||||
def test_state_change_tracked(benchmark: BenchmarkFixture, hass: HomeAssistant) -> None:
|
||||
"""Fire a state change routed to a tracked entity's listener.
|
||||
|
||||
This is the real automation hot path: ``async_set`` fires
|
||||
EVENT_STATE_CHANGED, the dispatcher does a dict lookup on the entity_id and
|
||||
runs the inline callback.
|
||||
"""
|
||||
fired = 0
|
||||
|
||||
@callback
|
||||
def listener(event: Event) -> None:
|
||||
nonlocal fired
|
||||
fired += 1
|
||||
|
||||
async_track_state_change_event(hass, "sensor.tracked", listener)
|
||||
counter = 0
|
||||
|
||||
def _set() -> None:
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
hass.states.async_set("sensor.tracked", str(counter))
|
||||
|
||||
benchmark(_set)
|
||||
|
||||
assert fired
|
||||
|
||||
|
||||
def test_state_change_untracked(
|
||||
benchmark: BenchmarkFixture, hass: HomeAssistant
|
||||
) -> None:
|
||||
"""Fire a state change for an entity nobody tracks (the dict-miss path).
|
||||
|
||||
Tracking is installed for a different entity, so the dispatcher's lookup
|
||||
misses and returns fast. This is the common case on a busy bus.
|
||||
"""
|
||||
async_track_state_change_event(hass, "sensor.tracked", _noop)
|
||||
counter = 0
|
||||
|
||||
def _set() -> None:
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
hass.states.async_set("sensor.untracked", str(counter))
|
||||
|
||||
benchmark(_set)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("receivers", [0, 1, 10])
|
||||
def test_dispatcher_send(
|
||||
benchmark: BenchmarkFixture, hass: HomeAssistant, receivers: int
|
||||
) -> None:
|
||||
"""Send a dispatcher signal to N connected receivers."""
|
||||
fired = 0
|
||||
|
||||
@callback
|
||||
def receiver(*args: object) -> None:
|
||||
nonlocal fired
|
||||
fired += 1
|
||||
|
||||
for _ in range(receivers):
|
||||
async_dispatcher_connect(hass, "benchmark_signal", receiver)
|
||||
|
||||
benchmark(lambda: async_dispatcher_send(hass, "benchmark_signal", 1))
|
||||
|
||||
|
||||
def test_call_later_schedule(benchmark: BenchmarkFixture, hass: HomeAssistant) -> None:
|
||||
"""Schedule a delayed callback and cancel it (the timer-tracking cost).
|
||||
|
||||
Cancelling inside the measured call keeps timers from piling up on the loop
|
||||
across iterations.
|
||||
"""
|
||||
|
||||
def _schedule() -> None:
|
||||
cancel: Callable[[], None] = async_call_later(hass, 60, _noop)
|
||||
cancel()
|
||||
|
||||
benchmark(_schedule)
|
||||
@@ -0,0 +1,138 @@
|
||||
"""CodSpeed benchmarks for the state machine and entity write path.
|
||||
|
||||
Every state update in Home Assistant flows through ``StateMachine.async_set``,
|
||||
and every entity that pushes an update lands in ``Entity._async_write_ha_state``.
|
||||
These are among the busiest call sites in the whole process.
|
||||
|
||||
Run locally with: ``pytest benchmarks --codspeed``.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from pytest_codspeed import BenchmarkFixture
|
||||
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
_ATTRS = {
|
||||
"friendly_name": "Benchmark light",
|
||||
"brightness": 255,
|
||||
"color_temp_kelvin": 4000,
|
||||
"supported_color_modes": ["color_temp", "rgb"],
|
||||
}
|
||||
|
||||
|
||||
def test_state_set_create(benchmark: BenchmarkFixture, hass: HomeAssistant) -> None:
|
||||
"""Write a brand new entity into the state machine (the create branch).
|
||||
|
||||
``pedantic`` with a teardown that removes the entity keeps every measured
|
||||
call on the create path; without it only the first call creates and the rest
|
||||
measure the update path.
|
||||
"""
|
||||
benchmark.pedantic(
|
||||
lambda: hass.states.async_set("light.benchmark", "on", _ATTRS),
|
||||
teardown=lambda: hass.states.async_remove("light.benchmark"),
|
||||
rounds=1000,
|
||||
)
|
||||
|
||||
|
||||
def test_state_set_update(benchmark: BenchmarkFixture, hass: HomeAssistant) -> None:
|
||||
"""Write a changed state for an existing entity (the update branch)."""
|
||||
counter = 0
|
||||
|
||||
def _set() -> None:
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
hass.states.async_set("light.benchmark", str(counter), _ATTRS)
|
||||
|
||||
benchmark(_set)
|
||||
|
||||
assert hass.states.get("light.benchmark") is not None
|
||||
|
||||
|
||||
def test_state_set_report(benchmark: BenchmarkFixture, hass: HomeAssistant) -> None:
|
||||
"""Re-set an unchanged state (the EVENT_STATE_REPORTED fast path).
|
||||
|
||||
Polling integrations hammer this branch: same value, same attributes, over
|
||||
and over. It fires a lightweight reported event instead of a state change.
|
||||
"""
|
||||
hass.states.async_set("sensor.benchmark", "21.5", _ATTRS)
|
||||
|
||||
benchmark(lambda: hass.states.async_set("sensor.benchmark", "21.5", _ATTRS))
|
||||
|
||||
|
||||
def test_state_get(benchmark: BenchmarkFixture, hass: HomeAssistant) -> None:
|
||||
"""Look a single state up by entity_id."""
|
||||
hass.states.async_set("sensor.benchmark", "21.5", _ATTRS)
|
||||
|
||||
state: State | None = benchmark(lambda: hass.states.get("sensor.benchmark"))
|
||||
|
||||
assert state is not None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("count", [10, 100, 1000])
|
||||
def test_state_all(
|
||||
benchmark: BenchmarkFixture,
|
||||
populate_states: Callable[[int], None],
|
||||
hass: HomeAssistant,
|
||||
count: int,
|
||||
) -> None:
|
||||
"""Read the full state list out of a populated machine, at several sizes."""
|
||||
populate_states(count)
|
||||
|
||||
states: list[State] = benchmark(hass.states.async_all)
|
||||
|
||||
assert len(states) == count
|
||||
|
||||
|
||||
@pytest.mark.parametrize("count", [10, 100, 1000])
|
||||
def test_state_entity_ids(
|
||||
benchmark: BenchmarkFixture,
|
||||
populate_states: Callable[[int], None],
|
||||
hass: HomeAssistant,
|
||||
count: int,
|
||||
) -> None:
|
||||
"""List entity ids out of a populated machine, at several sizes."""
|
||||
populate_states(count)
|
||||
|
||||
entity_ids = benchmark(hass.states.async_entity_ids)
|
||||
|
||||
assert len(entity_ids) == count
|
||||
|
||||
|
||||
class _BenchmarkEntity(Entity):
|
||||
"""A minimal entity carrying capability and extra state attributes."""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_name = "Benchmark"
|
||||
_attr_supported_features = 3
|
||||
|
||||
def __init__(self, state: str) -> None:
|
||||
"""Initialize the benchmark entity."""
|
||||
self._attr_state = state
|
||||
self._attr_extra_state_attributes: dict[str, Any] = {
|
||||
"brightness": 255,
|
||||
"color_temp_kelvin": 4000,
|
||||
}
|
||||
|
||||
@property
|
||||
def capability_attributes(self) -> dict[str, Any]:
|
||||
"""Return capability attributes, assembled on every write."""
|
||||
return {"supported_color_modes": ["color_temp", "rgb"]}
|
||||
|
||||
|
||||
def test_entity_write(benchmark: BenchmarkFixture, hass: HomeAssistant) -> None:
|
||||
"""Write an entity's state through the entity layer.
|
||||
|
||||
This measures ``__async_calculate_state`` (assembling state and attributes
|
||||
from the entity's properties) plus the ``async_set`` it lands in.
|
||||
"""
|
||||
entity = _BenchmarkEntity("on")
|
||||
entity.hass = hass
|
||||
entity.entity_id = "light.benchmark"
|
||||
|
||||
benchmark(entity._async_write_ha_state) # noqa: SLF001
|
||||
|
||||
assert hass.states.get("light.benchmark") is not None
|
||||
@@ -0,0 +1,112 @@
|
||||
"""CodSpeed benchmarks for the template engine.
|
||||
|
||||
Templates render on dashboards, in automations and in many entity attributes.
|
||||
Both the compile step and the warm render matter, and templates that walk the
|
||||
state machine scale with the number of entities.
|
||||
|
||||
Run locally with: ``pytest benchmarks --codspeed``.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
import pytest
|
||||
from pytest_codspeed import BenchmarkFixture
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.template import Template
|
||||
|
||||
_STATES_TEMPLATE = (
|
||||
"{{ (states('sensor.power') | float / 1000) | round(2) }} kW "
|
||||
"{{ is_state('binary_sensor.motion', 'on') }} "
|
||||
"{{ state_attr('sensor.power', 'unit_of_measurement') }}"
|
||||
)
|
||||
|
||||
|
||||
def test_template_compile(benchmark: BenchmarkFixture, hass: HomeAssistant) -> None:
|
||||
"""Compile a template from source (parse plus codegen).
|
||||
|
||||
A unique source on every call (a varying Jinja comment) keeps the
|
||||
environment's template cache missing, so each run actually compiles instead
|
||||
of returning cached bytecode. The comment is stripped during compilation, so
|
||||
the cost matches the real template.
|
||||
"""
|
||||
counter = 0
|
||||
|
||||
def _compile() -> Template:
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
template = Template(f"{{# {counter} #}}{_STATES_TEMPLATE}", hass)
|
||||
template.ensure_valid()
|
||||
return template
|
||||
|
||||
template = benchmark(_compile)
|
||||
|
||||
assert template.is_static is False
|
||||
|
||||
|
||||
def test_template_render_simple(
|
||||
benchmark: BenchmarkFixture, hass: HomeAssistant
|
||||
) -> None:
|
||||
"""Render a pure-math template (warm), the engine's baseline overhead."""
|
||||
template = Template("{{ 1 + 1 }}", hass)
|
||||
template.ensure_valid()
|
||||
|
||||
result = benchmark(template.async_render)
|
||||
|
||||
assert result == 2
|
||||
|
||||
|
||||
def test_template_render_states(
|
||||
benchmark: BenchmarkFixture, hass: HomeAssistant
|
||||
) -> None:
|
||||
"""Render a template that reads states, attributes and a filter (warm)."""
|
||||
hass.states.async_set("sensor.power", "1200", {"unit_of_measurement": "W"})
|
||||
hass.states.async_set("binary_sensor.motion", "on")
|
||||
template = Template(_STATES_TEMPLATE, hass)
|
||||
template.ensure_valid()
|
||||
|
||||
result = benchmark(template.async_render)
|
||||
|
||||
assert result.startswith("1.2 kW")
|
||||
|
||||
|
||||
def test_template_render_to_info(
|
||||
benchmark: BenchmarkFixture, hass: HomeAssistant
|
||||
) -> None:
|
||||
"""Render and collect the entity dependency filter (the tracking path).
|
||||
|
||||
``async_render_to_info`` is what template triggers and template entities use
|
||||
to learn which entities to subscribe to.
|
||||
"""
|
||||
hass.states.async_set("sensor.power", "1200", {"unit_of_measurement": "W"})
|
||||
hass.states.async_set("binary_sensor.motion", "on")
|
||||
template = Template(_STATES_TEMPLATE, hass)
|
||||
template.ensure_valid()
|
||||
|
||||
info = benchmark(template.async_render_to_info)
|
||||
|
||||
assert info.entities or info.all_states
|
||||
|
||||
|
||||
@pytest.mark.parametrize("count", [10, 100, 1000])
|
||||
def test_template_iterate_states(
|
||||
benchmark: BenchmarkFixture,
|
||||
populate_states: Callable[[int], None],
|
||||
hass: HomeAssistant,
|
||||
count: int,
|
||||
) -> None:
|
||||
"""Render a template that walks every sensor state, at several sizes.
|
||||
|
||||
This is where an O(n) template touches an O(n) state machine; the cost
|
||||
should grow linearly and a worse-than-linear regression should stand out.
|
||||
"""
|
||||
populate_states(count)
|
||||
template = Template(
|
||||
"{{ states.sensor | selectattr('state', 'eq', '1') | list | count }}",
|
||||
hass,
|
||||
)
|
||||
template.ensure_valid()
|
||||
|
||||
result = benchmark(template.async_render)
|
||||
|
||||
assert result == 1
|
||||
@@ -8,7 +8,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyenphase"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pyenphase==3.0.1"],
|
||||
"requirements": ["pyenphase==3.0.0"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_enphase-envoy._tcp.local."
|
||||
|
||||
@@ -227,8 +227,8 @@ class MikrotikData:
|
||||
_LOGGER.debug("Running command %s", cmd)
|
||||
try:
|
||||
if params:
|
||||
return list(self.api(cmd, **params))
|
||||
return list(self.api(cmd))
|
||||
return list(self.api(cmd=cmd, **params))
|
||||
return list(self.api(cmd=cmd))
|
||||
except (
|
||||
librouteros.exceptions.ConnectionClosed,
|
||||
OSError,
|
||||
@@ -318,7 +318,8 @@ def get_api(entry: dict[str, Any]) -> librouteros.Api:
|
||||
"""Connect to Mikrotik hub."""
|
||||
_LOGGER.debug("Connecting to Mikrotik hub [%s]", entry[CONF_HOST])
|
||||
|
||||
kwargs = {"port": entry["port"], "encoding": "utf8"}
|
||||
_login_method = (login_plain, login_token)
|
||||
kwargs = {"login_methods": _login_method, "port": entry["port"], "encoding": "utf8"}
|
||||
|
||||
if entry[CONF_VERIFY_SSL]:
|
||||
ssl_context = ssl.create_default_context()
|
||||
@@ -327,30 +328,22 @@ def get_api(entry: dict[str, Any]) -> librouteros.Api:
|
||||
_ssl_wrapper = ssl_context.wrap_socket
|
||||
kwargs["ssl_wrapper"] = _ssl_wrapper
|
||||
|
||||
_error: Exception | None = None
|
||||
for method in (login_plain, login_token):
|
||||
try:
|
||||
kwargs["login_method"] = method
|
||||
api = librouteros.connect(
|
||||
entry[CONF_HOST],
|
||||
entry[CONF_USERNAME],
|
||||
entry[CONF_PASSWORD],
|
||||
**kwargs,
|
||||
)
|
||||
_error = None
|
||||
break
|
||||
except (
|
||||
librouteros.exceptions.LibRouterosError,
|
||||
OSError,
|
||||
TimeoutError,
|
||||
) as api_error:
|
||||
_error = api_error
|
||||
|
||||
if _error is not None:
|
||||
_LOGGER.error("Mikrotik %s error: %s", entry[CONF_HOST], _error)
|
||||
if "invalid user name or password" in str(_error):
|
||||
raise LoginError from _error
|
||||
raise CannotConnect from _error
|
||||
try:
|
||||
api = librouteros.connect(
|
||||
entry[CONF_HOST],
|
||||
entry[CONF_USERNAME],
|
||||
entry[CONF_PASSWORD],
|
||||
**kwargs,
|
||||
)
|
||||
except (
|
||||
librouteros.exceptions.LibRouterosError,
|
||||
OSError,
|
||||
TimeoutError,
|
||||
) as api_error:
|
||||
_LOGGER.error("Mikrotik %s error: %s", entry[CONF_HOST], api_error)
|
||||
if "invalid user name or password" in str(api_error):
|
||||
raise LoginError from api_error
|
||||
raise CannotConnect from api_error
|
||||
|
||||
_LOGGER.debug("Connected to %s successfully", entry[CONF_HOST])
|
||||
return api
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["librouteros"],
|
||||
"requirements": ["librouteros==4.1.1"]
|
||||
"requirements": ["librouteros==3.2.1"]
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ from .const import (
|
||||
DEFAULT_QOS,
|
||||
DEFAULT_TRANSPORT,
|
||||
DEFAULT_WILL,
|
||||
DEFAULT_WS_HEADERS,
|
||||
DEFAULT_WS_PATH,
|
||||
DOMAIN,
|
||||
MQTT_CONNECTION_STATE,
|
||||
@@ -413,7 +414,7 @@ class MqttClientSetup:
|
||||
tls_insecure = config.get(CONF_TLS_INSECURE)
|
||||
if transport == TRANSPORT_WEBSOCKETS:
|
||||
ws_path: str = config.get(CONF_WS_PATH, DEFAULT_WS_PATH)
|
||||
ws_headers: dict[str, str] = config.get(CONF_WS_HEADERS, {})
|
||||
ws_headers: dict[str, str] = config.get(CONF_WS_HEADERS, DEFAULT_WS_HEADERS)
|
||||
self._client.ws_set_options(ws_path, ws_headers)
|
||||
if certificate is not None:
|
||||
self._client.tls_set(
|
||||
|
||||
@@ -373,6 +373,7 @@ from .const import (
|
||||
DEFAULT_CLIMATE_INITIAL_TEMPERATURE,
|
||||
DEFAULT_DISCOVERY,
|
||||
DEFAULT_ENCODING,
|
||||
DEFAULT_KEEPALIVE,
|
||||
DEFAULT_ON_COMMAND_TYPE,
|
||||
DEFAULT_PAYLOAD_ARM_AWAY,
|
||||
DEFAULT_PAYLOAD_ARM_CUSTOM_BYPASS,
|
||||
@@ -413,6 +414,7 @@ from .const import (
|
||||
DEFAULT_TILT_OPEN_POSITION,
|
||||
DEFAULT_TRANSPORT,
|
||||
DEFAULT_WILL,
|
||||
DEFAULT_WS_PATH,
|
||||
DOMAIN,
|
||||
REMOTE_CODE,
|
||||
REMOTE_CODE_TEXT,
|
||||
@@ -439,7 +441,7 @@ ADDON_SETUP_TIMEOUT_ROUNDS = 5
|
||||
|
||||
CONF_CLIENT_KEY_PASSWORD = "client_key_password"
|
||||
|
||||
OTHER_SETTINGS = "other_settings"
|
||||
ADVANCED_OPTIONS = "advanced_options"
|
||||
SET_CA_CERT = "set_ca_cert"
|
||||
SET_CLIENT_CERT = "set_client_cert"
|
||||
|
||||
@@ -1122,7 +1124,7 @@ def validate_light_platform_config(user_data: dict[str, Any]) -> dict[str, str]:
|
||||
if user_data.get(CONF_MIN_KELVIN, DEFAULT_MIN_KELVIN) >= user_data.get(
|
||||
CONF_MAX_KELVIN, DEFAULT_MAX_KELVIN
|
||||
):
|
||||
errors[OTHER_SETTINGS] = "max_below_min_kelvin"
|
||||
errors["other_settings"] = "max_below_min_kelvin"
|
||||
return errors
|
||||
|
||||
|
||||
@@ -1504,7 +1506,7 @@ PLATFORM_ENTITY_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
selector=SUGGESTED_DISPLAY_PRECISION_SELECTOR,
|
||||
required=False,
|
||||
validator=cv.positive_int,
|
||||
section=OTHER_SETTINGS,
|
||||
section="other_settings",
|
||||
),
|
||||
CONF_OPTIONS: PlatformField(
|
||||
selector=OPTIONS_SELECTOR,
|
||||
@@ -1676,13 +1678,13 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
selector=TIMEOUT_SELECTOR,
|
||||
required=False,
|
||||
validator=cv.positive_int,
|
||||
section=OTHER_SETTINGS,
|
||||
section="other_settings",
|
||||
),
|
||||
CONF_OFF_DELAY: PlatformField(
|
||||
selector=TIMEOUT_SELECTOR,
|
||||
required=False,
|
||||
validator=cv.positive_int,
|
||||
section=OTHER_SETTINGS,
|
||||
section="other_settings",
|
||||
),
|
||||
},
|
||||
Platform.BUTTON: {
|
||||
@@ -3123,7 +3125,7 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
default=False,
|
||||
validator=cv.boolean,
|
||||
conditions=({CONF_SCHEMA: "json"},),
|
||||
section=OTHER_SETTINGS,
|
||||
section="other_settings",
|
||||
),
|
||||
CONF_FLASH_TIME_SHORT: PlatformField(
|
||||
selector=FLASH_TIME_SELECTOR,
|
||||
@@ -3131,7 +3133,7 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
validator=cv.positive_int,
|
||||
default=2,
|
||||
conditions=({CONF_SCHEMA: "json"},),
|
||||
section=OTHER_SETTINGS,
|
||||
section="other_settings",
|
||||
),
|
||||
CONF_FLASH_TIME_LONG: PlatformField(
|
||||
selector=FLASH_TIME_SELECTOR,
|
||||
@@ -3139,7 +3141,7 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
validator=cv.positive_int,
|
||||
default=10,
|
||||
conditions=({CONF_SCHEMA: "json"},),
|
||||
section=OTHER_SETTINGS,
|
||||
section="other_settings",
|
||||
),
|
||||
CONF_TRANSITION: PlatformField(
|
||||
selector=BOOLEAN_SELECTOR,
|
||||
@@ -3147,21 +3149,21 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
default=False,
|
||||
validator=cv.boolean,
|
||||
conditions=({CONF_SCHEMA: "json"},),
|
||||
section=OTHER_SETTINGS,
|
||||
section="other_settings",
|
||||
),
|
||||
CONF_MAX_KELVIN: PlatformField(
|
||||
selector=KELVIN_SELECTOR,
|
||||
required=False,
|
||||
validator=cv.positive_int,
|
||||
default=DEFAULT_MAX_KELVIN,
|
||||
section=OTHER_SETTINGS,
|
||||
section="other_settings",
|
||||
),
|
||||
CONF_MIN_KELVIN: PlatformField(
|
||||
selector=KELVIN_SELECTOR,
|
||||
required=False,
|
||||
validator=cv.positive_int,
|
||||
default=DEFAULT_MIN_KELVIN,
|
||||
section=OTHER_SETTINGS,
|
||||
section="other_settings",
|
||||
),
|
||||
},
|
||||
Platform.LOCK: {
|
||||
@@ -3370,7 +3372,7 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
selector=TIMEOUT_SELECTOR,
|
||||
required=False,
|
||||
validator=cv.positive_int,
|
||||
section=OTHER_SETTINGS,
|
||||
section="other_settings",
|
||||
),
|
||||
},
|
||||
Platform.SIREN: {
|
||||
@@ -3796,10 +3798,10 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
|
||||
MQTT_DEVICE_PLATFORM_FIELDS = {
|
||||
CONF_NAME: PlatformField(selector=TEXT_SELECTOR, required=True),
|
||||
CONF_SW_VERSION: PlatformField(
|
||||
selector=TEXT_SELECTOR, required=False, section=OTHER_SETTINGS
|
||||
selector=TEXT_SELECTOR, required=False, section="other_settings"
|
||||
),
|
||||
CONF_HW_VERSION: PlatformField(
|
||||
selector=TEXT_SELECTOR, required=False, section=OTHER_SETTINGS
|
||||
selector=TEXT_SELECTOR, required=False, section="other_settings"
|
||||
),
|
||||
CONF_MODEL: PlatformField(selector=TEXT_SELECTOR, required=False),
|
||||
CONF_MODEL_ID: PlatformField(selector=TEXT_SELECTOR, required=False),
|
||||
@@ -4034,22 +4036,24 @@ def subentry_schema_default_data_from_fields(
|
||||
@callback
|
||||
def update_password_from_user_input(
|
||||
entry_password: str | None, user_input: dict[str, Any]
|
||||
) -> None:
|
||||
) -> dict[str, Any]:
|
||||
"""Update the password if the entry has been updated.
|
||||
|
||||
As we want to avoid reflecting the stored password in the UI,
|
||||
we replace the suggested value in the UI with a sentitel,
|
||||
and we change it back here if it was changed.
|
||||
"""
|
||||
substituted_used_data = dict(user_input)
|
||||
# Take out the password submitted
|
||||
user_password: str | None = user_input.pop(CONF_PASSWORD, None)
|
||||
user_password: str | None = substituted_used_data.pop(CONF_PASSWORD, None)
|
||||
# Only add the password if it has changed.
|
||||
# If the sentinel password is submitted, we replace that with our current
|
||||
# password from the config entry data.
|
||||
password_changed = user_password is not None and user_password != PWD_NOT_CHANGED
|
||||
password = user_password if password_changed else entry_password
|
||||
if password is not None:
|
||||
user_input[CONF_PASSWORD] = password
|
||||
substituted_used_data[CONF_PASSWORD] = password
|
||||
return substituted_used_data
|
||||
|
||||
|
||||
REAUTH_SCHEMA = vol.Schema(
|
||||
@@ -4059,35 +4063,6 @@ REAUTH_SCHEMA = vol.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
OTHER_SETTINGS_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_CLIENT_ID): TEXT_SELECTOR,
|
||||
vol.Optional(CONF_KEEPALIVE): KEEPALIVE_SELECTOR,
|
||||
vol.Required(SET_CLIENT_CERT): BOOLEAN_SELECTOR,
|
||||
vol.Optional(CONF_CLIENT_CERT): CERT_UPLOAD_SELECTOR,
|
||||
vol.Optional(CONF_CLIENT_KEY): CERT_KEY_UPLOAD_SELECTOR,
|
||||
vol.Optional(CONF_CLIENT_KEY_PASSWORD): PASSWORD_SELECTOR,
|
||||
vol.Required(SET_CA_CERT): BROKER_VERIFICATION_SELECTOR,
|
||||
vol.Optional(CONF_CERTIFICATE): CA_CERT_UPLOAD_SELECTOR,
|
||||
vol.Optional(CONF_TLS_INSECURE): BOOLEAN_SELECTOR,
|
||||
vol.Required(CONF_TRANSPORT, default=DEFAULT_TRANSPORT): TRANSPORT_SELECTOR,
|
||||
vol.Optional(CONF_WS_PATH): TEXT_SELECTOR,
|
||||
vol.Optional(CONF_WS_HEADERS): WS_HEADERS_SELECTOR,
|
||||
}
|
||||
)
|
||||
CONFIG_DATAFLOW_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_BROKER): TEXT_SELECTOR,
|
||||
vol.Required(CONF_PORT, default=DEFAULT_PORT): PORT_SELECTOR,
|
||||
vol.Required(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): PROTOCOL_SELECTOR,
|
||||
vol.Optional(CONF_USERNAME): TEXT_SELECTOR,
|
||||
vol.Optional(CONF_PASSWORD): PASSWORD_SELECTOR,
|
||||
vol.Required(OTHER_SETTINGS): section(
|
||||
OTHER_SETTINGS_SCHEMA, SectionConfig({"collapsed": True})
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class FlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow."""
|
||||
@@ -4097,26 +4072,24 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
_hassio_discovery: dict[str, Any] | None = None
|
||||
_addon_manager: AddonManager
|
||||
last_uploaded: dict[str, Any]
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Set up flow instance."""
|
||||
self.install_task: asyncio.Task | None = None
|
||||
self.start_task: asyncio.Task | None = None
|
||||
self.last_uploaded = {}
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
@callback
|
||||
@override
|
||||
def async_get_supported_subentry_types(
|
||||
cls, config_entry: ConfigEntry
|
||||
) -> dict[str, type[ConfigSubentryFlow]]:
|
||||
"""Return subentries supported by this handler."""
|
||||
return {CONF_DEVICE: MQTTSubentryFlowHandler}
|
||||
|
||||
@override
|
||||
@staticmethod
|
||||
@callback
|
||||
@override
|
||||
def async_get_options_flow(
|
||||
config_entry: ConfigEntry,
|
||||
) -> MQTTOptionsFlowHandler:
|
||||
@@ -4337,9 +4310,8 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
reauth_entry = self._get_reauth_entry()
|
||||
if user_input:
|
||||
substituted_used_data = deepcopy(user_input)
|
||||
update_password_from_user_input(
|
||||
reauth_entry.data.get(CONF_PASSWORD), substituted_used_data
|
||||
substituted_used_data = update_password_from_user_input(
|
||||
reauth_entry.data.get(CONF_PASSWORD), user_input
|
||||
)
|
||||
new_entry_data = {**reauth_entry.data, **substituted_used_data}
|
||||
if await self.hass.async_add_executor_job(
|
||||
@@ -4363,76 +4335,49 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_get_entry_defaults(self) -> dict[str, Any]:
|
||||
"""Load the default settings from the entry."""
|
||||
data = self._get_reconfigure_entry().data
|
||||
other_settings: dict[str, Any] = {
|
||||
key.schema: data[key.schema]
|
||||
for key in OTHER_SETTINGS_SCHEMA.schema
|
||||
if key in data
|
||||
}
|
||||
other_settings[SET_CLIENT_CERT] = (CONF_CLIENT_CERT in other_settings) and (
|
||||
CONF_CLIENT_KEY in other_settings
|
||||
)
|
||||
other_settings.pop(CONF_CLIENT_CERT, None)
|
||||
other_settings.pop(CONF_CLIENT_KEY, None)
|
||||
conf_cert = other_settings.pop(CONF_CERTIFICATE, None)
|
||||
other_settings[SET_CA_CERT] = (
|
||||
"auto"
|
||||
if conf_cert == "auto"
|
||||
else "custom"
|
||||
if conf_cert is not None
|
||||
else "off"
|
||||
)
|
||||
if CONF_WS_HEADERS in other_settings:
|
||||
other_settings[CONF_WS_HEADERS] = json_dumps(
|
||||
other_settings.pop(CONF_WS_HEADERS)
|
||||
)
|
||||
|
||||
settings: dict[str, Any] = {
|
||||
key.schema: data[key.schema]
|
||||
for key in CONFIG_DATAFLOW_SCHEMA.schema
|
||||
if key in data
|
||||
}
|
||||
settings[OTHER_SETTINGS] = other_settings
|
||||
if CONF_PASSWORD in settings:
|
||||
# Hide entry password
|
||||
settings[CONF_PASSWORD] = PWD_NOT_CHANGED
|
||||
return settings
|
||||
|
||||
async def async_step_broker(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Confirm the setup."""
|
||||
errors: dict[str, str] = {}
|
||||
schema = CONFIG_DATAFLOW_SCHEMA
|
||||
entry_config_update: dict[str, Any] = {}
|
||||
entry_defaults: dict[str, Any] | None = None
|
||||
fields: OrderedDict[Any, Any] = OrderedDict()
|
||||
validated_user_input: dict[str, Any] = {}
|
||||
if is_reconfigure := (self.source == SOURCE_RECONFIGURE):
|
||||
reconfigure_entry = self._get_reconfigure_entry()
|
||||
entry_defaults = self.async_get_entry_defaults()
|
||||
if await async_validate_broker_settings(
|
||||
if await async_get_broker_settings(
|
||||
self,
|
||||
fields,
|
||||
reconfigure_entry.data if is_reconfigure else None,
|
||||
user_input,
|
||||
entry_config_update,
|
||||
validated_user_input,
|
||||
errors,
|
||||
):
|
||||
if is_reconfigure:
|
||||
return self.async_update_and_abort(
|
||||
reconfigure_entry,
|
||||
data=entry_config_update,
|
||||
validated_user_input = update_password_from_user_input(
|
||||
reconfigure_entry.data.get(CONF_PASSWORD), validated_user_input
|
||||
)
|
||||
return self.async_create_entry(
|
||||
title=entry_config_update[CONF_BROKER],
|
||||
data=entry_config_update,
|
||||
|
||||
can_connect = await self.hass.async_add_executor_job(
|
||||
try_connection,
|
||||
validated_user_input,
|
||||
)
|
||||
|
||||
schema = self.add_suggested_values_to_schema(
|
||||
schema, (entry_defaults or {}) | (user_input or {})
|
||||
if can_connect:
|
||||
if is_reconfigure:
|
||||
return self.async_update_and_abort(
|
||||
reconfigure_entry,
|
||||
data=validated_user_input,
|
||||
)
|
||||
return self.async_create_entry(
|
||||
title=validated_user_input[CONF_BROKER],
|
||||
data=validated_user_input,
|
||||
)
|
||||
|
||||
errors["base"] = "cannot_connect"
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="broker", data_schema=vol.Schema(fields), errors=errors
|
||||
)
|
||||
return self.async_show_form(step_id="broker", data_schema=schema, errors=errors)
|
||||
|
||||
async def async_step_reconfigure(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -4743,8 +4688,8 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
|
||||
if user_input is not None:
|
||||
new_device_data: dict[str, Any] = user_input.copy()
|
||||
_, errors = validate_user_input(user_input, MQTT_DEVICE_PLATFORM_FIELDS)
|
||||
if OTHER_SETTINGS in new_device_data:
|
||||
new_device_data |= new_device_data.pop(OTHER_SETTINGS)
|
||||
if "other_settings" in new_device_data:
|
||||
new_device_data |= new_device_data.pop("other_settings")
|
||||
if not errors:
|
||||
self._subentry_data[CONF_DEVICE] = cast(MqttDeviceData, new_device_data)
|
||||
if self.source == SOURCE_RECONFIGURE:
|
||||
@@ -5305,162 +5250,331 @@ async def _get_uploaded_file(hass: HomeAssistant, id: str) -> bytes:
|
||||
return await hass.async_add_executor_job(_proces_uploaded_file)
|
||||
|
||||
|
||||
async def async_validate_broker_settings(
|
||||
flow: FlowHandler,
|
||||
entry_config: MappingProxyType[str, Any] | None,
|
||||
user_input: dict[str, Any] | None,
|
||||
entry_config_update: dict[str, Any],
|
||||
errors: dict[str, str],
|
||||
def _validate_pki_file(
|
||||
file_id: str | None, pem_data: str | None, errors: dict[str, str], error: str
|
||||
) -> bool:
|
||||
"""Validate the broker settings, and return the updated entry dataset."""
|
||||
|
||||
async def _async_process_file_upload(
|
||||
upload_id: str,
|
||||
field: str,
|
||||
pem_type: PEMType,
|
||||
error_code: str,
|
||||
password: str | None = None,
|
||||
) -> bool:
|
||||
"""Get uploaded file, or a preserved copy, and convert to a PEM file."""
|
||||
try:
|
||||
data_raw = await _get_uploaded_file(hass, upload_id)
|
||||
except ValueError:
|
||||
# Use preserved file if available.
|
||||
# When an uploaded file was read, but an error occurs,
|
||||
# the form will reload but the temporary file from the upload
|
||||
# will not be available any more. If it was processed correctly,
|
||||
# we can use the preserved copy.
|
||||
if upload_id in flow.last_uploaded:
|
||||
data_raw = flow.last_uploaded[upload_id]
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
# Preserve a copy in case the validation fails,
|
||||
# and we need it later
|
||||
flow.last_uploaded[upload_id] = data_raw
|
||||
pem_data = async_convert_to_pem(data_raw, pem_type, password)
|
||||
if upload_id and not pem_data:
|
||||
errors["base"] = error_code
|
||||
return False
|
||||
entry_config_update[field] = pem_data
|
||||
return True
|
||||
|
||||
if user_input is None:
|
||||
return False
|
||||
|
||||
hass = flow.hass
|
||||
|
||||
# Copy basic and other entry fields
|
||||
entry_config_update |= user_input
|
||||
entry_config_update.update(entry_config_update.pop(OTHER_SETTINGS))
|
||||
# Pop incompatible fields for update
|
||||
for key in (
|
||||
SET_CA_CERT,
|
||||
SET_CLIENT_CERT,
|
||||
CONF_CERTIFICATE,
|
||||
CONF_CLIENT_CERT,
|
||||
CONF_CLIENT_KEY,
|
||||
CONF_CLIENT_KEY_PASSWORD,
|
||||
):
|
||||
entry_config_update.pop(key, None)
|
||||
|
||||
# Get current CA certificate settings from config entry
|
||||
if (set_ca_cert := user_input[OTHER_SETTINGS][SET_CA_CERT]) == "auto":
|
||||
entry_config_update[CONF_CERTIFICATE] = "auto"
|
||||
elif (
|
||||
entry_config is not None
|
||||
and set_ca_cert == "custom"
|
||||
and (current_cert := entry_config.get(CONF_CERTIFICATE))
|
||||
):
|
||||
entry_config_update[CONF_CERTIFICATE] = current_cert
|
||||
|
||||
# Prepare entry update with uploaded certificate files
|
||||
# converted to PEM format
|
||||
new_client_certificate: str | None = user_input[OTHER_SETTINGS].get(
|
||||
CONF_CLIENT_CERT
|
||||
)
|
||||
new_client_key: str | None = user_input[OTHER_SETTINGS].get(CONF_CLIENT_KEY)
|
||||
set_client_cert = user_input[OTHER_SETTINGS][SET_CLIENT_CERT]
|
||||
|
||||
if (new_client_certificate and not new_client_key) or (
|
||||
not new_client_certificate and new_client_key
|
||||
):
|
||||
errors["base"] = "invalid_inclusion"
|
||||
return False
|
||||
|
||||
if new_certificate := user_input[OTHER_SETTINGS].get(CONF_CERTIFICATE):
|
||||
if not await _async_process_file_upload(
|
||||
new_certificate, CONF_CERTIFICATE, PEMType.CERTIFICATE, "bad_certificate"
|
||||
):
|
||||
return False
|
||||
|
||||
if new_client_certificate:
|
||||
if not await _async_process_file_upload(
|
||||
new_client_certificate,
|
||||
CONF_CLIENT_CERT,
|
||||
PEMType.CERTIFICATE,
|
||||
"bad_client_cert",
|
||||
):
|
||||
return False
|
||||
elif (
|
||||
entry_config is not None
|
||||
and set_client_cert
|
||||
and (client_cert := entry_config.get(CONF_CLIENT_CERT))
|
||||
):
|
||||
entry_config_update[CONF_CLIENT_CERT] = client_cert
|
||||
|
||||
if new_client_key:
|
||||
if not await _async_process_file_upload(
|
||||
new_client_key,
|
||||
CONF_CLIENT_KEY,
|
||||
PEMType.PRIVATE_KEY,
|
||||
"client_key_error",
|
||||
password=user_input[OTHER_SETTINGS].get(CONF_CLIENT_KEY_PASSWORD),
|
||||
):
|
||||
return False
|
||||
elif (
|
||||
entry_config is not None
|
||||
and set_client_cert
|
||||
and (client_key := entry_config.get(CONF_CLIENT_KEY))
|
||||
):
|
||||
entry_config_update[CONF_CLIENT_KEY] = client_key
|
||||
|
||||
# We temporarily create the current and new uploaded certificate files
|
||||
# and we check the certificate chain.
|
||||
await async_create_certificate_temp_files(hass, entry_config_update)
|
||||
if error := await hass.async_add_executor_job(
|
||||
check_certicate_chain,
|
||||
):
|
||||
"""Return False if uploaded file could not be converted to PEM format."""
|
||||
if file_id and not pem_data:
|
||||
errors["base"] = error
|
||||
return False
|
||||
return True
|
||||
|
||||
if user_input[OTHER_SETTINGS].get(CONF_TRANSPORT, TRANSPORT_TCP) == TRANSPORT_TCP:
|
||||
entry_config_update.pop(CONF_WS_PATH, None)
|
||||
entry_config_update.pop(CONF_WS_HEADERS, None)
|
||||
else:
|
||||
# Web socket transport
|
||||
try:
|
||||
entry_config_update[CONF_WS_HEADERS] = json_loads(
|
||||
user_input[OTHER_SETTINGS].get(CONF_WS_HEADERS, "{}")
|
||||
|
||||
async def async_get_broker_settings(
|
||||
flow: ConfigFlow | OptionsFlow,
|
||||
fields: OrderedDict[Any, Any],
|
||||
entry_config: MappingProxyType[str, Any] | None,
|
||||
user_input: dict[str, Any] | None,
|
||||
validated_user_input: dict[str, Any],
|
||||
errors: dict[str, str],
|
||||
) -> bool:
|
||||
"""Build the config flow schema to collect the broker settings.
|
||||
|
||||
Shows advanced options if one or more are configured
|
||||
or when the advanced_broker_options checkbox was selected.
|
||||
Returns True when settings are collected successfully.
|
||||
"""
|
||||
hass = flow.hass
|
||||
advanced_broker_options: bool = False
|
||||
user_input_basic: dict[str, Any] = {}
|
||||
current_config: dict[str, Any] = (
|
||||
entry_config.copy() if entry_config is not None else {}
|
||||
)
|
||||
|
||||
async def _async_validate_broker_settings(
|
||||
config: dict[str, Any],
|
||||
user_input: dict[str, Any],
|
||||
validated_user_input: dict[str, Any],
|
||||
errors: dict[str, str],
|
||||
) -> bool:
|
||||
"""Additional validation on broker settings for better error messages."""
|
||||
|
||||
if CONF_PROTOCOL not in validated_user_input:
|
||||
validated_user_input[CONF_PROTOCOL] = DEFAULT_PROTOCOL
|
||||
# Get current certificate settings from config entry
|
||||
certificate: str | None = (
|
||||
"auto"
|
||||
if user_input.get(SET_CA_CERT, "off") == "auto"
|
||||
else config.get(CONF_CERTIFICATE)
|
||||
if user_input.get(SET_CA_CERT, "off") == "custom"
|
||||
else None
|
||||
)
|
||||
client_certificate: str | None = (
|
||||
config.get(CONF_CLIENT_CERT) if user_input.get(SET_CLIENT_CERT) else None
|
||||
)
|
||||
client_key: str | None = (
|
||||
config.get(CONF_CLIENT_KEY) if user_input.get(SET_CLIENT_CERT) else None
|
||||
)
|
||||
|
||||
# Prepare entry update with uploaded files
|
||||
validated_user_input.update(user_input)
|
||||
client_certificate_id: str | None = user_input.get(CONF_CLIENT_CERT)
|
||||
client_key_id: str | None = user_input.get(CONF_CLIENT_KEY)
|
||||
# We do not store the private key password in the entry data
|
||||
client_key_password: str | None = validated_user_input.pop(
|
||||
CONF_CLIENT_KEY_PASSWORD, None
|
||||
)
|
||||
if (client_certificate_id and not client_key_id) or (
|
||||
not client_certificate_id and client_key_id
|
||||
):
|
||||
errors["base"] = "invalid_inclusion"
|
||||
return False
|
||||
certificate_id: str | None = user_input.get(CONF_CERTIFICATE)
|
||||
if certificate_id:
|
||||
certificate_data_raw = await _get_uploaded_file(hass, certificate_id)
|
||||
certificate = async_convert_to_pem(
|
||||
certificate_data_raw, PEMType.CERTIFICATE
|
||||
)
|
||||
schema = vol.Schema({str: str})
|
||||
schema(entry_config_update[CONF_WS_HEADERS])
|
||||
if not _validate_pki_file(
|
||||
certificate_id, certificate, errors, "bad_certificate"
|
||||
):
|
||||
return False
|
||||
|
||||
# Return to form for file upload CA cert or client cert and key
|
||||
if (
|
||||
(
|
||||
not client_certificate
|
||||
and user_input.get(SET_CLIENT_CERT)
|
||||
and not client_certificate_id
|
||||
)
|
||||
or (
|
||||
not certificate
|
||||
and user_input.get(SET_CA_CERT, "off") == "custom"
|
||||
and not certificate_id
|
||||
)
|
||||
or (
|
||||
user_input.get(CONF_TRANSPORT) == TRANSPORT_WEBSOCKETS
|
||||
and CONF_WS_PATH not in user_input
|
||||
)
|
||||
):
|
||||
return False
|
||||
|
||||
if client_certificate_id:
|
||||
client_certificate_data = await _get_uploaded_file(
|
||||
hass, client_certificate_id
|
||||
)
|
||||
client_certificate = async_convert_to_pem(
|
||||
client_certificate_data, PEMType.CERTIFICATE
|
||||
)
|
||||
if not _validate_pki_file(
|
||||
client_certificate_id, client_certificate, errors, "bad_client_cert"
|
||||
):
|
||||
return False
|
||||
|
||||
if client_key_id:
|
||||
client_key_data = await _get_uploaded_file(hass, client_key_id)
|
||||
client_key = async_convert_to_pem(
|
||||
client_key_data, PEMType.PRIVATE_KEY, password=client_key_password
|
||||
)
|
||||
if not _validate_pki_file(
|
||||
client_key_id, client_key, errors, "client_key_error"
|
||||
):
|
||||
return False
|
||||
|
||||
certificate_data: dict[str, Any] = {}
|
||||
if certificate:
|
||||
certificate_data[CONF_CERTIFICATE] = certificate
|
||||
if client_certificate:
|
||||
certificate_data[CONF_CLIENT_CERT] = client_certificate
|
||||
certificate_data[CONF_CLIENT_KEY] = client_key
|
||||
|
||||
validated_user_input.update(certificate_data)
|
||||
await async_create_certificate_temp_files(hass, certificate_data)
|
||||
if error := await hass.async_add_executor_job(
|
||||
check_certicate_chain,
|
||||
):
|
||||
errors["base"] = error
|
||||
return False
|
||||
|
||||
validated_user_input.pop(SET_CA_CERT, None)
|
||||
validated_user_input.pop(SET_CLIENT_CERT, None)
|
||||
if validated_user_input.get(CONF_TRANSPORT, TRANSPORT_TCP) == TRANSPORT_TCP:
|
||||
validated_user_input.pop(CONF_WS_PATH, None)
|
||||
validated_user_input.pop(CONF_WS_HEADERS, None)
|
||||
return True
|
||||
try:
|
||||
validated_user_input[CONF_WS_HEADERS] = json_loads(
|
||||
validated_user_input.get(CONF_WS_HEADERS, "{}")
|
||||
)
|
||||
schema = vol.Schema({cv.string: cv.template})
|
||||
schema(validated_user_input[CONF_WS_HEADERS])
|
||||
except (*JSON_DECODE_EXCEPTIONS, vol.MultipleInvalid):
|
||||
errors["base"] = "bad_ws_headers"
|
||||
return False
|
||||
|
||||
# Test the configuration
|
||||
if entry_config is not None:
|
||||
update_password_from_user_input(
|
||||
entry_config.get(CONF_PASSWORD), entry_config_update
|
||||
)
|
||||
if await hass.async_add_executor_job(
|
||||
try_connection,
|
||||
entry_config_update,
|
||||
):
|
||||
return True
|
||||
|
||||
errors["base"] = "cannot_connect"
|
||||
if user_input:
|
||||
user_input_basic = user_input.copy()
|
||||
advanced_broker_options = user_input_basic.get(ADVANCED_OPTIONS, False)
|
||||
if ADVANCED_OPTIONS not in user_input or advanced_broker_options is False:
|
||||
if await _async_validate_broker_settings(
|
||||
current_config,
|
||||
user_input_basic,
|
||||
validated_user_input,
|
||||
errors,
|
||||
):
|
||||
return True
|
||||
# Get defaults settings from previous post
|
||||
current_broker = user_input_basic.get(CONF_BROKER)
|
||||
current_port = user_input_basic.get(CONF_PORT, DEFAULT_PORT)
|
||||
current_user = user_input_basic.get(CONF_USERNAME)
|
||||
current_pass = user_input_basic.get(CONF_PASSWORD)
|
||||
else:
|
||||
# Get default settings from entry (if any)
|
||||
current_broker = current_config.get(CONF_BROKER)
|
||||
current_port = current_config.get(CONF_PORT, DEFAULT_PORT)
|
||||
current_user = current_config.get(CONF_USERNAME)
|
||||
# Return the sentinel password to avoid exposure
|
||||
current_entry_pass = current_config.get(CONF_PASSWORD)
|
||||
current_pass = PWD_NOT_CHANGED if current_entry_pass else None
|
||||
|
||||
# Treat the previous post as an update of the current settings
|
||||
# (if there was a basic broker setup step)
|
||||
current_config.update(user_input_basic)
|
||||
|
||||
# Get default settings for advanced broker options
|
||||
current_client_id = current_config.get(CONF_CLIENT_ID)
|
||||
current_keepalive = current_config.get(CONF_KEEPALIVE, DEFAULT_KEEPALIVE)
|
||||
current_ca_certificate = current_config.get(CONF_CERTIFICATE)
|
||||
current_client_certificate = current_config.get(CONF_CLIENT_CERT)
|
||||
current_client_key = current_config.get(CONF_CLIENT_KEY)
|
||||
current_tls_insecure = current_config.get(CONF_TLS_INSECURE, False)
|
||||
current_protocol = current_config.get(CONF_PROTOCOL, DEFAULT_PROTOCOL)
|
||||
current_transport = current_config.get(CONF_TRANSPORT, DEFAULT_TRANSPORT)
|
||||
current_ws_path = current_config.get(CONF_WS_PATH, DEFAULT_WS_PATH)
|
||||
current_ws_headers = (
|
||||
json_dumps(current_config.get(CONF_WS_HEADERS))
|
||||
if CONF_WS_HEADERS in current_config
|
||||
else None
|
||||
)
|
||||
advanced_broker_options |= bool(
|
||||
current_client_id
|
||||
or current_keepalive != DEFAULT_KEEPALIVE
|
||||
or current_ca_certificate
|
||||
or current_client_certificate
|
||||
or current_client_key
|
||||
or current_tls_insecure
|
||||
or current_config.get(SET_CA_CERT, "off") != "off"
|
||||
or current_config.get(SET_CLIENT_CERT)
|
||||
or current_transport == TRANSPORT_WEBSOCKETS
|
||||
)
|
||||
|
||||
# Build form
|
||||
fields[vol.Required(CONF_BROKER, default=current_broker)] = TEXT_SELECTOR
|
||||
fields[vol.Required(CONF_PORT, default=current_port)] = PORT_SELECTOR
|
||||
fields[
|
||||
vol.Optional(
|
||||
CONF_PROTOCOL,
|
||||
description={"suggested_value": current_protocol},
|
||||
)
|
||||
] = PROTOCOL_SELECTOR
|
||||
fields[
|
||||
vol.Optional(
|
||||
CONF_USERNAME,
|
||||
description={"suggested_value": current_user},
|
||||
)
|
||||
] = TEXT_SELECTOR
|
||||
fields[
|
||||
vol.Optional(
|
||||
CONF_PASSWORD,
|
||||
description={"suggested_value": current_pass},
|
||||
)
|
||||
] = PASSWORD_SELECTOR
|
||||
# show advanced options checkbox if no defaults
|
||||
# of the advanced options are overridden
|
||||
if not advanced_broker_options:
|
||||
fields[
|
||||
vol.Optional(
|
||||
ADVANCED_OPTIONS,
|
||||
)
|
||||
] = BOOLEAN_SELECTOR
|
||||
return False
|
||||
fields[
|
||||
vol.Optional(
|
||||
CONF_CLIENT_ID,
|
||||
description={"suggested_value": current_client_id},
|
||||
)
|
||||
] = TEXT_SELECTOR
|
||||
fields[
|
||||
vol.Optional(
|
||||
CONF_KEEPALIVE,
|
||||
description={"suggested_value": current_keepalive},
|
||||
)
|
||||
] = KEEPALIVE_SELECTOR
|
||||
fields[
|
||||
vol.Optional(
|
||||
SET_CLIENT_CERT,
|
||||
default=current_client_certificate is not None
|
||||
or current_config.get(SET_CLIENT_CERT) is True,
|
||||
)
|
||||
] = BOOLEAN_SELECTOR
|
||||
if (
|
||||
current_client_certificate is not None
|
||||
or current_config.get(SET_CLIENT_CERT) is True
|
||||
):
|
||||
fields[
|
||||
vol.Optional(
|
||||
CONF_CLIENT_CERT,
|
||||
description={"suggested_value": user_input_basic.get(CONF_CLIENT_CERT)},
|
||||
)
|
||||
] = CERT_UPLOAD_SELECTOR
|
||||
fields[
|
||||
vol.Optional(
|
||||
CONF_CLIENT_KEY,
|
||||
description={"suggested_value": user_input_basic.get(CONF_CLIENT_KEY)},
|
||||
)
|
||||
] = CERT_KEY_UPLOAD_SELECTOR
|
||||
fields[
|
||||
vol.Optional(
|
||||
CONF_CLIENT_KEY_PASSWORD,
|
||||
description={
|
||||
"suggested_value": user_input_basic.get(CONF_CLIENT_KEY_PASSWORD)
|
||||
},
|
||||
)
|
||||
] = PASSWORD_SELECTOR
|
||||
verification_mode = current_config.get(SET_CA_CERT) or (
|
||||
"off"
|
||||
if current_ca_certificate is None
|
||||
else "auto"
|
||||
if current_ca_certificate == "auto"
|
||||
else "custom"
|
||||
)
|
||||
fields[
|
||||
vol.Optional(
|
||||
SET_CA_CERT,
|
||||
default=verification_mode,
|
||||
)
|
||||
] = BROKER_VERIFICATION_SELECTOR
|
||||
if current_ca_certificate is not None or verification_mode == "custom":
|
||||
fields[
|
||||
vol.Optional(
|
||||
CONF_CERTIFICATE,
|
||||
user_input_basic.get(CONF_CERTIFICATE),
|
||||
)
|
||||
] = CA_CERT_UPLOAD_SELECTOR
|
||||
fields[
|
||||
vol.Optional(
|
||||
CONF_TLS_INSECURE,
|
||||
description={"suggested_value": current_tls_insecure},
|
||||
)
|
||||
] = BOOLEAN_SELECTOR
|
||||
fields[
|
||||
vol.Optional(
|
||||
CONF_TRANSPORT,
|
||||
description={"suggested_value": current_transport},
|
||||
)
|
||||
] = TRANSPORT_SELECTOR
|
||||
if current_transport == TRANSPORT_WEBSOCKETS:
|
||||
fields[
|
||||
vol.Optional(CONF_WS_PATH, description={"suggested_value": current_ws_path})
|
||||
] = TEXT_SELECTOR
|
||||
fields[
|
||||
vol.Optional(
|
||||
CONF_WS_HEADERS, description={"suggested_value": current_ws_headers}
|
||||
)
|
||||
] = WS_HEADERS_SELECTOR
|
||||
|
||||
# Show form
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -315,6 +315,7 @@ DEFAULT_TILT_MAX = 100
|
||||
DEFAULT_TILT_MIN = 0
|
||||
DEFAULT_TILT_OPEN_POSITION = 100
|
||||
DEFAULT_TILT_OPTIMISTIC = False
|
||||
DEFAULT_WS_HEADERS: dict[str, str] = {}
|
||||
DEFAULT_WS_PATH = "/"
|
||||
DEFAULT_POSITION_CLOSED = 0
|
||||
DEFAULT_POSITION_OPEN = 100
|
||||
|
||||
@@ -26,53 +26,46 @@
|
||||
"step": {
|
||||
"broker": {
|
||||
"data": {
|
||||
"advanced_options": "Advanced options",
|
||||
"broker": "Broker",
|
||||
"certificate": "Upload custom CA certificate file",
|
||||
"client_cert": "Upload client certificate file",
|
||||
"client_id": "Client ID (leave empty to randomly generated one)",
|
||||
"client_key": "Upload private key file",
|
||||
"client_key_password": "[%key:common::config_flow::data::password%]",
|
||||
"keepalive": "The time between sending keep alive messages",
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"port": "[%key:common::config_flow::data::port%]",
|
||||
"protocol": "MQTT protocol",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
"set_ca_cert": "Broker certificate validation",
|
||||
"set_client_cert": "Use a client certificate",
|
||||
"tls_insecure": "Ignore broker certificate validation",
|
||||
"transport": "MQTT transport",
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"ws_headers": "WebSocket headers in JSON format",
|
||||
"ws_path": "WebSocket path"
|
||||
},
|
||||
"data_description": {
|
||||
"advanced_options": "Enable and select **Submit** to set advanced options.",
|
||||
"broker": "The hostname or IP address of your MQTT broker.",
|
||||
"certificate": "The custom CA certificate file to validate your MQTT broker's certificate.",
|
||||
"client_cert": "The client certificate to authenticate against your MQTT broker.",
|
||||
"client_id": "The unique ID to identify the Home Assistant MQTT API as MQTT client. It is recommended to leave this option blank.",
|
||||
"client_key": "The private key file that belongs to your client certificate.",
|
||||
"client_key_password": "The password for the private key file (if set).",
|
||||
"keepalive": "A value less than 90 seconds is advised.",
|
||||
"password": "The password to log in to your MQTT broker.",
|
||||
"port": "The port your MQTT broker listens to. For example 1883.",
|
||||
"protocol": "The MQTT protocol version that is used. Note that Home Assistant will silently change to version 5 if your broker supports it.",
|
||||
"username": "The username to log in to your MQTT broker."
|
||||
"set_ca_cert": "Select **Auto** for automatic CA validation, or **Custom** and select **Next** to set a custom CA certificate, to allow validating your MQTT broker's certificate.",
|
||||
"set_client_cert": "Enable and select **Next** to set a client certificate and private key to authenticate against your MQTT broker.",
|
||||
"tls_insecure": "Option to ignore validation of your MQTT broker's certificate.",
|
||||
"transport": "The transport to be used for the connection to your MQTT broker.",
|
||||
"username": "The username to log in to your MQTT broker.",
|
||||
"ws_headers": "The WebSocket headers to pass through the WebSocket-based connection to your MQTT broker.",
|
||||
"ws_path": "The WebSocket path to be used for the connection to your MQTT broker."
|
||||
},
|
||||
"description": "Please enter the connection information of your MQTT broker.",
|
||||
"sections": {
|
||||
"other_settings": {
|
||||
"data": {
|
||||
"certificate": "Upload custom CA certificate file",
|
||||
"client_cert": "Upload client certificate file",
|
||||
"client_id": "Client ID (leave empty for a randomly generated one)",
|
||||
"client_key": "Upload private key file",
|
||||
"client_key_password": "[%key:common::config_flow::data::password%]",
|
||||
"keepalive": "The time between sending keep alive messages",
|
||||
"set_ca_cert": "Broker certificate validation",
|
||||
"set_client_cert": "Use a client certificate",
|
||||
"tls_insecure": "Ignore broker certificate validation",
|
||||
"transport": "MQTT transport",
|
||||
"ws_headers": "WebSocket headers in JSON format",
|
||||
"ws_path": "WebSocket path"
|
||||
},
|
||||
"data_description": {
|
||||
"certificate": "The custom CA certificate file to validate your MQTT broker's certificate.",
|
||||
"client_cert": "The client certificate to authenticate against your MQTT broker.",
|
||||
"client_id": "The unique ID to identify the Home Assistant MQTT API as MQTT client. It is recommended to leave this option blank.",
|
||||
"client_key": "The private key file that belongs to your client certificate.",
|
||||
"client_key_password": "The password for the private key file (if set).",
|
||||
"keepalive": "A value less than 90 seconds is advised. Defaults to 60 seconds.",
|
||||
"set_ca_cert": "When already set to **Custom**, a custom CA validation certificate is configured. Select **Auto** for automatic CA validation, or upload a custom CA certificate, to allow validating your MQTT broker's certificate.",
|
||||
"set_client_cert": "When already selected, client certificate authentication is enabled. Upload a client certificate and key to enable.",
|
||||
"tls_insecure": "Option to ignore validation of your MQTT broker's certificate.",
|
||||
"transport": "The transport to be used for the connection to your MQTT broker.",
|
||||
"ws_headers": "The WebSocket headers to pass through the WebSocket-based connection to your MQTT broker.",
|
||||
"ws_path": "The WebSocket path to be used for the connection to your MQTT broker."
|
||||
},
|
||||
"name": "Other settings"
|
||||
}
|
||||
}
|
||||
"description": "Please enter the connection information of your MQTT broker."
|
||||
},
|
||||
"hassio_confirm": {
|
||||
"description": "Do you want to configure Home Assistant to connect to the MQTT broker provided by the {addon} app?",
|
||||
@@ -1185,6 +1178,48 @@
|
||||
"invalid_inclusion": "[%key:component::mqtt::config::error::invalid_inclusion%]"
|
||||
},
|
||||
"step": {
|
||||
"broker": {
|
||||
"data": {
|
||||
"advanced_options": "[%key:component::mqtt::config::step::broker::data::advanced_options%]",
|
||||
"broker": "[%key:component::mqtt::config::step::broker::data::broker%]",
|
||||
"certificate": "[%key:component::mqtt::config::step::broker::data::certificate%]",
|
||||
"client_cert": "[%key:component::mqtt::config::step::broker::data::client_cert%]",
|
||||
"client_id": "[%key:component::mqtt::config::step::broker::data::client_id%]",
|
||||
"client_key": "[%key:component::mqtt::config::step::broker::data::client_key%]",
|
||||
"keepalive": "[%key:component::mqtt::config::step::broker::data::keepalive%]",
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"port": "[%key:common::config_flow::data::port%]",
|
||||
"protocol": "[%key:component::mqtt::config::step::broker::data::protocol%]",
|
||||
"set_ca_cert": "[%key:component::mqtt::config::step::broker::data::set_ca_cert%]",
|
||||
"set_client_cert": "[%key:component::mqtt::config::step::broker::data::set_client_cert%]",
|
||||
"tls_insecure": "[%key:component::mqtt::config::step::broker::data::tls_insecure%]",
|
||||
"transport": "[%key:component::mqtt::config::step::broker::data::transport%]",
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"ws_headers": "[%key:component::mqtt::config::step::broker::data::ws_headers%]",
|
||||
"ws_path": "[%key:component::mqtt::config::step::broker::data::ws_path%]"
|
||||
},
|
||||
"data_description": {
|
||||
"advanced_options": "[%key:component::mqtt::config::step::broker::data_description::advanced_options%]",
|
||||
"broker": "[%key:component::mqtt::config::step::broker::data_description::broker%]",
|
||||
"certificate": "[%key:component::mqtt::config::step::broker::data_description::certificate%]",
|
||||
"client_cert": "[%key:component::mqtt::config::step::broker::data_description::client_cert%]",
|
||||
"client_id": "[%key:component::mqtt::config::step::broker::data_description::client_id%]",
|
||||
"client_key": "[%key:component::mqtt::config::step::broker::data_description::client_key%]",
|
||||
"keepalive": "[%key:component::mqtt::config::step::broker::data_description::keepalive%]",
|
||||
"password": "[%key:component::mqtt::config::step::broker::data_description::password%]",
|
||||
"port": "[%key:component::mqtt::config::step::broker::data_description::port%]",
|
||||
"protocol": "[%key:component::mqtt::config::step::broker::data_description::protocol%]",
|
||||
"set_ca_cert": "[%key:component::mqtt::config::step::broker::data_description::set_ca_cert%]",
|
||||
"set_client_cert": "[%key:component::mqtt::config::step::broker::data_description::set_client_cert%]",
|
||||
"tls_insecure": "[%key:component::mqtt::config::step::broker::data_description::tls_insecure%]",
|
||||
"transport": "[%key:component::mqtt::config::step::broker::data_description::transport%]",
|
||||
"username": "[%key:component::mqtt::config::step::broker::data_description::username%]",
|
||||
"ws_headers": "[%key:component::mqtt::config::step::broker::data_description::ws_headers%]",
|
||||
"ws_path": "[%key:component::mqtt::config::step::broker::data_description::ws_path%]"
|
||||
},
|
||||
"description": "[%key:component::mqtt::config::step::broker::description%]",
|
||||
"title": "Broker options"
|
||||
},
|
||||
"options": {
|
||||
"data": {
|
||||
"birth_enable": "Enable birth message",
|
||||
|
||||
@@ -437,6 +437,13 @@ runtime-typing = false
|
||||
[tool.pylint.CODE_STYLE]
|
||||
max-line-length-suggestions = 72
|
||||
|
||||
[tool.uv]
|
||||
# surepy (a surepetcare dependency) carries a stale upper pin on rich (<11) that
|
||||
# blocks pytest-codspeed (needs rich>=13.8.1) from resolving in the same
|
||||
# environment. rich keeps the Console API surepy uses backwards compatible, so
|
||||
# override the pin rather than holding the benchmark tooling back.
|
||||
override-dependencies = ["rich>=13.8.1"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = ["pylint/plugins"]
|
||||
testpaths = ["tests"]
|
||||
@@ -877,6 +884,9 @@ split-on-trailing-comma = false
|
||||
"homeassistant/components/*/*/*" = ["TID252"]
|
||||
"tests/components/*/*/*" = ["TID252"]
|
||||
|
||||
# Benchmarks reuse the test helpers to spin up a Home Assistant instance
|
||||
"benchmarks/**" = ["TID251"]
|
||||
|
||||
# Temporary
|
||||
"homeassistant/**" = ["PTH"]
|
||||
"tests/**" = ["PTH"]
|
||||
|
||||
Generated
+2
-2
@@ -1474,7 +1474,7 @@ libpyvivotek==0.6.1
|
||||
librehardwaremonitor-api==1.11.1
|
||||
|
||||
# homeassistant.components.mikrotik
|
||||
librouteros==4.1.1
|
||||
librouteros==3.2.1
|
||||
|
||||
# homeassistant.components.soundtouch
|
||||
libsoundtouch==0.8
|
||||
@@ -2148,7 +2148,7 @@ pyegps==0.2.5
|
||||
pyemoncms==0.1.3
|
||||
|
||||
# homeassistant.components.enphase_envoy
|
||||
pyenphase==3.0.1
|
||||
pyenphase==3.0.0
|
||||
|
||||
# homeassistant.components.envertech_evt800
|
||||
pyenvertechevt800==0.2.4
|
||||
|
||||
@@ -25,6 +25,7 @@ pylint-per-file-ignores==3.2.1
|
||||
pipdeptree==2.26.1
|
||||
pytest-asyncio==1.4.0
|
||||
pytest-aiohttp==1.1.1
|
||||
pytest-codspeed==5.0.3
|
||||
pytest-cov==7.1.0
|
||||
pytest-freezer==0.4.9
|
||||
pytest-github-actions-annotate-failures==0.4.2
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,19 +2,7 @@
|
||||
|
||||
import contextlib
|
||||
|
||||
from astroid import nodes
|
||||
from pylint.checkers import BaseChecker
|
||||
from pylint.testutils.unittest_linter import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
|
||||
|
||||
def walk_checker(
|
||||
linter: UnittestLinter, checker: BaseChecker, node: nodes.NodeNG
|
||||
) -> None:
|
||||
"""Run the given checker over the parsed node."""
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
walker.walk(node)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.actions.service_registration import (
|
||||
ServiceRegistrationChecker,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from tests.pylint import assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_no_messages
|
||||
|
||||
|
||||
@pytest.fixture(name="registration_checker")
|
||||
@@ -67,9 +68,11 @@ def test_no_warning(
|
||||
) -> None:
|
||||
"""Test cases that should not trigger a warning."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(registration_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, registration_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_hass_services_register_flagged(
|
||||
@@ -84,7 +87,9 @@ async def async_setup_entry(hass, entry):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walk_checker(linter, registration_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(registration_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -103,7 +108,9 @@ async def async_setup_entry(hass, entry):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walk_checker(linter, registration_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(registration_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -121,7 +128,9 @@ async def async_setup_entry(hass, entry):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walk_checker(linter, registration_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(registration_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -139,7 +148,9 @@ async def async_setup_entry(hass, entry):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walk_checker(linter, registration_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(registration_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -159,7 +170,9 @@ async def async_setup_entry(hass, entry):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walk_checker(linter, registration_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(registration_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 3
|
||||
@@ -180,7 +193,9 @@ async def async_setup_entry(hass, entry):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walk_checker(linter, registration_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(registration_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -204,6 +219,8 @@ async def async_setup_entry(hass, entry):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(registration_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, registration_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.actions.swallowed_exceptions import (
|
||||
SwallowedActionExceptionsChecker,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from tests.pylint import assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_no_messages
|
||||
|
||||
|
||||
@pytest.fixture(name="error_propagation_checker")
|
||||
@@ -128,9 +129,11 @@ def test_no_warning(
|
||||
) -> None:
|
||||
"""Test cases that should not trigger a warning."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.test_integration.switch")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_log_only_flagged(
|
||||
@@ -149,7 +152,9 @@ class MySwitch(SwitchEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.switch",
|
||||
)
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -174,7 +179,9 @@ class MySwitch(SwitchEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.switch",
|
||||
)
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -196,7 +203,9 @@ class MySwitch(SwitchEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.switch",
|
||||
)
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -220,7 +229,9 @@ class MySwitch(SwitchEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.switch",
|
||||
)
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -244,7 +255,9 @@ class MySwitch(SwitchEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.switch",
|
||||
)
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -267,7 +280,9 @@ class MySwitch(SwitchEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.switch",
|
||||
)
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -289,7 +304,9 @@ class MySwitch(SwitchEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.switch",
|
||||
)
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -317,7 +334,9 @@ class MySwitch(SwitchEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.switch",
|
||||
)
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 2
|
||||
@@ -337,7 +356,9 @@ class MySwitch(SwitchEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.switch",
|
||||
)
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -358,9 +379,11 @@ class MySwitch(SwitchEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.switch",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_decorator_swallows_flagged(
|
||||
@@ -385,7 +408,9 @@ class MySwitch(SwitchEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.switch",
|
||||
)
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -416,7 +441,9 @@ class MySwitch(SwitchEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.switch",
|
||||
)
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -444,9 +471,11 @@ class MySwitch(SwitchEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.switch",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_custom_service_method_flagged(
|
||||
@@ -473,7 +502,9 @@ class MyFan(FanEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.fan",
|
||||
)
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -504,9 +535,11 @@ class MyFan(FanEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.fan",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_unregistered_custom_method_ignored(
|
||||
@@ -525,9 +558,11 @@ class MyFan(FanEntity):
|
||||
""",
|
||||
"homeassistant.components.test_integration.fan",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_standalone_service_handler_flagged(
|
||||
@@ -548,7 +583,9 @@ async def _handle_do_thing(call):
|
||||
""",
|
||||
"homeassistant.components.test_integration.services",
|
||||
)
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -573,7 +610,9 @@ async def _handle_reset(call):
|
||||
""",
|
||||
"homeassistant.components.test_integration.services",
|
||||
)
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -598,9 +637,11 @@ async def _handle_do_thing(call):
|
||||
""",
|
||||
"homeassistant.components.test_integration.services",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_not_integration_module_ignored(
|
||||
@@ -619,6 +660,8 @@ class MySwitch(SwitchEntity):
|
||||
""",
|
||||
"tests.components.test_integration.test_switch",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(error_propagation_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, error_propagation_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -8,9 +8,10 @@ from pathlib import Path
|
||||
import astroid
|
||||
from pylint.checkers import BaseChecker
|
||||
from pylint.testutils.unittest_linter import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
import pytest
|
||||
|
||||
from tests.pylint import assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_no_messages
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -61,9 +62,11 @@ def test_enforce_config_flow_no_name(
|
||||
) -> None:
|
||||
"""Good test cases."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_config_flow_no_name_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_config_flow_no_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -107,8 +110,10 @@ def test_enforce_config_flow_no_name_bad(
|
||||
) -> None:
|
||||
"""Bad test cases."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_config_flow_no_name_checker)
|
||||
|
||||
walk_checker(linter, enforce_config_flow_no_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
assert messages[0].msg_id == "home-assistant-config-flow-name-field"
|
||||
@@ -129,9 +134,11 @@ def test_enforce_config_flow_no_name_subentry_flow(
|
||||
)
|
||||
"""
|
||||
root_node = astroid.parse(code, "homeassistant.components.test.config_flow")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_config_flow_no_name_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_config_flow_no_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_enforce_config_flow_no_name_helper_integration(
|
||||
@@ -153,8 +160,11 @@ def test_enforce_config_flow_no_name_helper_integration(
|
||||
root_node = astroid.parse(code, "homeassistant.components.my_helper.config_flow")
|
||||
root_node.file = str(integration_dir / "config_flow.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_config_flow_no_name_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_config_flow_no_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_enforce_config_flow_no_name_non_helper_integration(
|
||||
@@ -174,7 +184,10 @@ def test_enforce_config_flow_no_name_non_helper_integration(
|
||||
root_node = astroid.parse(code, "homeassistant.components.my_device.config_flow")
|
||||
root_node.file = str(integration_dir / "config_flow.py")
|
||||
|
||||
walk_checker(linter, enforce_config_flow_no_name_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_config_flow_no_name_checker)
|
||||
|
||||
walker.walk(root_node)
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
assert messages[0].msg_id == "home-assistant-config-flow-name-field"
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
import astroid
|
||||
from pylint.checkers import BaseChecker
|
||||
from pylint.testutils.unittest_linter import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
import pytest
|
||||
|
||||
from tests.pylint import assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_no_messages
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -70,9 +71,11 @@ def test_enforce_config_flow_no_polling(
|
||||
) -> None:
|
||||
"""Good test cases."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_config_flow_no_polling_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_config_flow_no_polling_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -130,8 +133,10 @@ def test_enforce_config_flow_no_polling_bad(
|
||||
) -> None:
|
||||
"""Bad test cases."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_config_flow_no_polling_checker)
|
||||
|
||||
walk_checker(linter, enforce_config_flow_no_polling_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
assert messages[0].msg_id == "home-assistant-config-flow-polling-field"
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
import astroid
|
||||
from pylint.checkers import BaseChecker
|
||||
from pylint.testutils.unittest_linter import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
import pytest
|
||||
|
||||
from tests.pylint import assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_no_messages
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -63,9 +64,11 @@ def test_enforce_unique_id_no_ip(
|
||||
) -> None:
|
||||
"""Good test cases."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_config_entry_unique_id_no_ip_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_config_entry_unique_id_no_ip_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -118,8 +121,10 @@ def test_enforce_unique_id_no_ip_bad_call(
|
||||
) -> None:
|
||||
"""Bad async_set_unique_id call test cases."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_config_entry_unique_id_no_ip_checker)
|
||||
|
||||
walk_checker(linter, enforce_config_entry_unique_id_no_ip_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
assert messages[0].msg_id == "home-assistant-unique-id-ip-based"
|
||||
@@ -177,8 +182,10 @@ def test_enforce_unique_id_no_ip_bad_call_variable(
|
||||
) -> None:
|
||||
"""Bad async_set_unique_id call test cases."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_config_entry_unique_id_no_ip_checker)
|
||||
|
||||
walk_checker(linter, enforce_config_entry_unique_id_no_ip_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
assert messages[0].msg_id == "home-assistant-unique-id-ip-based"
|
||||
|
||||
@@ -4,6 +4,7 @@ from pathlib import Path
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import MessageTest, UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.quality_scale.config_entry_unloading import (
|
||||
ConfigEntryUnloadingChecker,
|
||||
)
|
||||
@@ -11,7 +12,7 @@ from pylint_home_assistant.helpers.quality_scale import clear_quality_scale_cach
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from tests.pylint import assert_adds_messages, assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_adds_messages, assert_no_messages
|
||||
|
||||
|
||||
@pytest.fixture(name="unloading_checker")
|
||||
@@ -55,8 +56,11 @@ async def async_unload_entry(hass, entry):
|
||||
)
|
||||
root_node.file = str(integration_dir / "__init__.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(unloading_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, unloading_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_unload_entry_missing_fires(
|
||||
@@ -77,6 +81,9 @@ async def async_setup_entry(hass, entry):
|
||||
)
|
||||
root_node.file = str(integration_dir / "__init__.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(unloading_checker)
|
||||
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
MessageTest(
|
||||
@@ -86,7 +93,7 @@ async def async_setup_entry(hass, entry):
|
||||
col_offset=0,
|
||||
),
|
||||
):
|
||||
walk_checker(linter, unloading_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -139,5 +146,8 @@ async def async_setup_entry(hass, entry):
|
||||
)
|
||||
root_node.file = str(integration_dir / "__init__.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(unloading_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, unloading_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -4,12 +4,13 @@ from pathlib import Path
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import MessageTest, UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.quality_scale.diagnostics import DiagnosticsChecker
|
||||
from pylint_home_assistant.helpers.quality_scale import clear_quality_scale_cache
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from tests.pylint import assert_adds_messages, assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_adds_messages, assert_no_messages
|
||||
|
||||
|
||||
@pytest.fixture(name="diagnostics_checker")
|
||||
@@ -74,8 +75,11 @@ def test_diagnostics_present(
|
||||
root_node = astroid.parse(code, "homeassistant.components.test_int.diagnostics")
|
||||
root_node.file = str(integration_dir / "diagnostics.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(diagnostics_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, diagnostics_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_diagnostics_missing_fires(
|
||||
@@ -96,6 +100,9 @@ async def async_setup(hass, config):
|
||||
)
|
||||
root_node.file = str(integration_dir / "diagnostics.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(diagnostics_checker)
|
||||
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
MessageTest(
|
||||
@@ -105,7 +112,7 @@ async def async_setup(hass, config):
|
||||
col_offset=0,
|
||||
),
|
||||
):
|
||||
walk_checker(linter, diagnostics_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -158,5 +165,8 @@ async def async_setup(hass, config):
|
||||
)
|
||||
root_node.file = str(integration_dir / "diagnostics.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(diagnostics_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, diagnostics_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -6,6 +6,7 @@ from pathlib import Path
|
||||
import astroid
|
||||
from astroid import nodes
|
||||
from pylint.testutils import MessageTest, UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.quality_scale.entity_unique_id import (
|
||||
EntityUniqueIdChecker,
|
||||
)
|
||||
@@ -14,7 +15,7 @@ from pylint_home_assistant.helpers.quality_scale import clear_quality_scale_cach
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from tests.pylint import assert_adds_messages, assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_adds_messages, assert_no_messages
|
||||
|
||||
|
||||
@pytest.fixture(name="entity_unique_id_checker")
|
||||
@@ -257,8 +258,10 @@ def test_handled(
|
||||
_create_quality_scale(integration_dir, {"entity-unique-id": "done"})
|
||||
|
||||
root_node = _parse(code, integration_dir)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -348,8 +351,10 @@ def test_ancestor_satisfies_rule(
|
||||
astroid.parse(ancestor_code, "homeassistant.components.test_integration.eui_entity")
|
||||
root_node = _parse(sensor_code, integration_dir)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_missing_fires(
|
||||
@@ -372,8 +377,10 @@ class MySensor(Entity):
|
||||
)
|
||||
|
||||
class_node = next(root_node.nodes_of_class(nodes.ClassDef))
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -437,8 +444,10 @@ def test_conditional_self_assignment_fires(
|
||||
for cls in root_node.nodes_of_class(nodes.ClassDef)
|
||||
if cls.name == class_name
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_explicit_none_class_body_fires(
|
||||
@@ -461,8 +470,10 @@ class MySensor(Entity):
|
||||
)
|
||||
|
||||
class_node = next(root_node.nodes_of_class(nodes.ClassDef))
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_subclass_nullifies_ancestor_value(
|
||||
@@ -499,8 +510,10 @@ class MySensor(TestIntegrationBaseEntity):
|
||||
for cls in root_node.nodes_of_class(nodes.ClassDef)
|
||||
if cls.name == "MySensor"
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_explicit_none_self_assign_fires(
|
||||
@@ -524,8 +537,10 @@ class MySensor(Entity):
|
||||
)
|
||||
|
||||
class_node = next(root_node.nodes_of_class(nodes.ClassDef))
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_bare_annotation_only_fires(
|
||||
@@ -548,8 +563,10 @@ class MySensor(Entity):
|
||||
)
|
||||
|
||||
class_node = next(root_node.nodes_of_class(nodes.ClassDef))
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_entity_default_does_not_satisfy(
|
||||
@@ -579,8 +596,10 @@ class MySensor(Entity):
|
||||
)
|
||||
|
||||
class_node = next(root_node.nodes_of_class(nodes.ClassDef))
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -619,8 +638,10 @@ def test_class_not_subject_to_rule(
|
||||
|
||||
root_node = _parse(code, integration_dir)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_leaf_class_still_fires(
|
||||
@@ -650,8 +671,10 @@ class SomethingUnrelated:
|
||||
for cls in root_node.nodes_of_class(nodes.ClassDef)
|
||||
if cls.name == "LonelySensor"
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_dict_status_done_fires(
|
||||
@@ -677,8 +700,10 @@ class MySensor(Entity):
|
||||
)
|
||||
|
||||
class_node = next(root_node.nodes_of_class(nodes.ClassDef))
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -748,8 +773,10 @@ class MySensor(Entity):
|
||||
"""
|
||||
root_node = _parse(code, integration_dir, module_name, file_name)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def _find_attr_value_node(
|
||||
@@ -821,8 +848,10 @@ def test_static_class_body_string_in_multi_entry_fires(
|
||||
|
||||
root_node = _parse(code, integration_dir)
|
||||
value_node = _find_attr_value_node(root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_adds_messages(linter, _expect_static(value_node, "MySensor")):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_static_class_body_string_no_manifest_fires(
|
||||
@@ -846,8 +875,10 @@ class MySensor(Entity):
|
||||
)
|
||||
|
||||
value_node = _find_attr_value_node(root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_adds_messages(linter, _expect_static(value_node, "MySensor")):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
_STATIC_CLASS_BODY_STRING = """
|
||||
@@ -915,5 +946,7 @@ def test_static_rule_does_not_warn(
|
||||
_create_quality_scale(integration_dir, {"entity-unique-id": rule_status})
|
||||
|
||||
root_node = _parse(code, integration_dir)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(entity_unique_id_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, entity_unique_id_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -5,6 +5,7 @@ from pathlib import Path
|
||||
import astroid
|
||||
from astroid import nodes
|
||||
from pylint.testutils import MessageTest, UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.quality_scale.has_entity_name import (
|
||||
HasEntityNameChecker,
|
||||
)
|
||||
@@ -12,7 +13,7 @@ from pylint_home_assistant.helpers.quality_scale import clear_quality_scale_cach
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from tests.pylint import assert_adds_messages, assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_adds_messages, assert_no_messages
|
||||
|
||||
|
||||
@pytest.fixture(name="has_entity_name_checker")
|
||||
@@ -144,8 +145,10 @@ def test_handled(
|
||||
_create_quality_scale(integration_dir, {"has-entity-name": "done"})
|
||||
|
||||
root_node = _parse(code, integration_dir)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_ancestor_class_level(
|
||||
@@ -177,8 +180,10 @@ class MySensor(TestIntegrationBaseEntity):
|
||||
integration_dir,
|
||||
)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_ancestor_self_assign(
|
||||
@@ -211,8 +216,10 @@ class MySensor(TestIntegrationBaseEntity):
|
||||
integration_dir,
|
||||
)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_missing_fires(
|
||||
@@ -235,8 +242,10 @@ class MySensor(Entity):
|
||||
)
|
||||
|
||||
class_node = next(root_node.nodes_of_class(nodes.ClassDef))
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -324,8 +333,10 @@ def test_conditional_self_assignment_fires(
|
||||
for cls in root_node.nodes_of_class(nodes.ClassDef)
|
||||
if cls.name == class_name
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_explicit_false_fires(
|
||||
@@ -348,8 +359,10 @@ class MySensor(Entity):
|
||||
)
|
||||
|
||||
class_node = next(root_node.nodes_of_class(nodes.ClassDef))
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_generic_subscript_base_sets_flag(
|
||||
@@ -381,8 +394,10 @@ class MySensor(TestIntegrationGenericBase[int]):
|
||||
integration_dir,
|
||||
)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_two_level_subscript_chain(
|
||||
@@ -423,8 +438,10 @@ class MyLight(TestIntegrationLightBase):
|
||||
file_name="light.py",
|
||||
)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_entity_description_fallback(
|
||||
@@ -454,8 +471,10 @@ class MyEntity(Entity):
|
||||
integration_dir,
|
||||
)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_entity_description_subscripted_annotation(
|
||||
@@ -485,8 +504,10 @@ class MyEntity[T](Entity):
|
||||
integration_dir,
|
||||
)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_entity_description_without_flag_still_fires(
|
||||
@@ -521,8 +542,10 @@ class MyEntity(Entity):
|
||||
for cls in root_node.nodes_of_class(nodes.ClassDef)
|
||||
if cls.name == "MyEntity"
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_entity_description_set_in_ancestor(
|
||||
@@ -562,8 +585,10 @@ class MySensor(TestIntegrationBaseEntity):
|
||||
integration_dir,
|
||||
)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_mixin_subclassed_in_same_module_ignored(
|
||||
@@ -588,8 +613,10 @@ class ActualEntity(MyClimateMixin):
|
||||
integration_dir,
|
||||
)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_subclassed_via_subscript_ignored(
|
||||
@@ -614,8 +641,10 @@ class ConcreteEntity(GenericBase[int]):
|
||||
integration_dir,
|
||||
)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_leaf_class_still_fires(
|
||||
@@ -645,8 +674,10 @@ class SomethingUnrelated:
|
||||
for cls in root_node.nodes_of_class(nodes.ClassDef)
|
||||
if cls.name == "LonelySensor"
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_non_entity_class_ignored(
|
||||
@@ -666,8 +697,10 @@ class NotAnEntity:
|
||||
integration_dir,
|
||||
)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_dict_status_done_fires(
|
||||
@@ -693,8 +726,10 @@ class MySensor(Entity):
|
||||
)
|
||||
|
||||
class_node = next(root_node.nodes_of_class(nodes.ClassDef))
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_adds_messages(linter, _expect_missing(class_node)):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -764,5 +799,7 @@ class MySensor(Entity):
|
||||
"""
|
||||
root_node = _parse(code, integration_dir, module_name, file_name)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(has_entity_name_checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, has_entity_name_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -4,6 +4,7 @@ from pathlib import Path
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import MessageTest, UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.quality_scale.parallel_updates import (
|
||||
ParallelUpdatesChecker,
|
||||
)
|
||||
@@ -11,7 +12,7 @@ from pylint_home_assistant.helpers.quality_scale import clear_quality_scale_cach
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from tests.pylint import assert_adds_messages, assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_adds_messages, assert_no_messages
|
||||
|
||||
|
||||
@pytest.fixture(name="parallel_updates_checker")
|
||||
@@ -60,8 +61,11 @@ def test_parallel_updates_present(
|
||||
root_node = astroid.parse("PARALLEL_UPDATES = 1\n", module_name)
|
||||
root_node.file = str(integration_dir / "sensor.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(parallel_updates_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, parallel_updates_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_parallel_updates_zero(
|
||||
@@ -78,8 +82,11 @@ def test_parallel_updates_zero(
|
||||
)
|
||||
root_node.file = str(integration_dir / "sensor.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(parallel_updates_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, parallel_updates_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_parallel_updates_annotated_assignment(
|
||||
@@ -97,8 +104,11 @@ def test_parallel_updates_annotated_assignment(
|
||||
)
|
||||
root_node.file = str(integration_dir / "sensor.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(parallel_updates_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, parallel_updates_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_parallel_updates_missing_fires(
|
||||
@@ -116,6 +126,9 @@ def test_parallel_updates_missing_fires(
|
||||
)
|
||||
root_node.file = str(integration_dir / "sensor.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(parallel_updates_checker)
|
||||
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
MessageTest(
|
||||
@@ -125,7 +138,7 @@ def test_parallel_updates_missing_fires(
|
||||
col_offset=0,
|
||||
),
|
||||
):
|
||||
walk_checker(linter, parallel_updates_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_parallel_updates_missing_status_done_dict(
|
||||
@@ -146,6 +159,9 @@ def test_parallel_updates_missing_status_done_dict(
|
||||
)
|
||||
root_node.file = str(integration_dir / "sensor.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(parallel_updates_checker)
|
||||
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
MessageTest(
|
||||
@@ -155,7 +171,7 @@ def test_parallel_updates_missing_status_done_dict(
|
||||
col_offset=0,
|
||||
),
|
||||
):
|
||||
walk_checker(linter, parallel_updates_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -215,5 +231,8 @@ def test_parallel_updates_not_fired(
|
||||
)
|
||||
root_node.file = str(integration_dir / "sensor.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(parallel_updates_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, parallel_updates_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -4,6 +4,7 @@ from pathlib import Path
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import MessageTest, UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.quality_scale.reauthentication_flow import (
|
||||
ReauthenticationFlowChecker,
|
||||
)
|
||||
@@ -11,7 +12,7 @@ from pylint_home_assistant.helpers.quality_scale import clear_quality_scale_cach
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from tests.pylint import assert_adds_messages, assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_adds_messages, assert_no_messages
|
||||
|
||||
|
||||
@pytest.fixture(name="reauth_checker")
|
||||
@@ -53,8 +54,11 @@ class MyConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
root_node.file = str(integration_dir / "config_flow.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(reauth_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, reauth_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_reauth_missing_fires(
|
||||
@@ -76,6 +80,9 @@ class MyConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
root_node.file = str(integration_dir / "config_flow.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(reauth_checker)
|
||||
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
MessageTest(
|
||||
@@ -85,7 +92,7 @@ class MyConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
col_offset=0,
|
||||
),
|
||||
):
|
||||
walk_checker(linter, reauth_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -139,5 +146,8 @@ class MyConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
root_node.file = str(integration_dir / "config_flow.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(reauth_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, reauth_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -5,9 +5,10 @@ from pylint.checkers import BaseChecker
|
||||
from pylint.interfaces import UNDEFINED
|
||||
from pylint.testutils import MessageTest
|
||||
from pylint.testutils.unittest_linter import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
import pytest
|
||||
|
||||
from . import assert_adds_messages, assert_no_messages, walk_checker
|
||||
from . import assert_adds_messages, assert_no_messages
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -53,9 +54,11 @@ def test_enforce_class_module_good(
|
||||
) -> None:
|
||||
"""Good test cases."""
|
||||
root_node = astroid.parse(code, path)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_class_module_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_class_module_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -87,9 +90,11 @@ def test_enforce_class_platform_good(
|
||||
pass
|
||||
"""
|
||||
root_node = astroid.parse(code, path)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_class_module_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_class_module_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -123,6 +128,8 @@ def test_enforce_class_module_bad_simple(
|
||||
""",
|
||||
path,
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_class_module_checker)
|
||||
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
@@ -147,7 +154,7 @@ def test_enforce_class_module_bad_simple(
|
||||
end_col_offset=35,
|
||||
),
|
||||
):
|
||||
walk_checker(linter, enforce_class_module_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -178,6 +185,8 @@ def test_enforce_class_module_bad_nested(
|
||||
""",
|
||||
path,
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_class_module_checker)
|
||||
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
@@ -202,7 +211,7 @@ def test_enforce_class_module_bad_nested(
|
||||
end_col_offset=21,
|
||||
),
|
||||
):
|
||||
walk_checker(linter, enforce_class_module_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -227,9 +236,11 @@ def test_enforce_entity_good(
|
||||
pass
|
||||
"""
|
||||
root_node = astroid.parse(code, path)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_class_module_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_class_module_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -254,6 +265,8 @@ def test_enforce_entity_bad(
|
||||
pass
|
||||
"""
|
||||
root_node = astroid.parse(code, path)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_class_module_checker)
|
||||
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
@@ -268,4 +281,4 @@ def test_enforce_entity_bad(
|
||||
end_col_offset=18,
|
||||
),
|
||||
):
|
||||
walk_checker(linter, enforce_class_module_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -5,9 +5,10 @@ from pylint.checkers import BaseChecker
|
||||
from pylint.interfaces import UNDEFINED
|
||||
from pylint.testutils import MessageTest
|
||||
from pylint.testutils.unittest_linter import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
import pytest
|
||||
|
||||
from . import assert_adds_messages, assert_no_messages, walk_checker
|
||||
from . import assert_adds_messages, assert_no_messages
|
||||
|
||||
|
||||
def test_good_callback(linter: UnittestLinter, decorator_checker: BaseChecker) -> None:
|
||||
@@ -23,9 +24,11 @@ def test_good_callback(linter: UnittestLinter, decorator_checker: BaseChecker) -
|
||||
"""
|
||||
|
||||
root_node = astroid.parse(code)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(decorator_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, decorator_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_bad_callback(linter: UnittestLinter, decorator_checker: BaseChecker) -> None:
|
||||
@@ -41,6 +44,8 @@ def test_bad_callback(linter: UnittestLinter, decorator_checker: BaseChecker) ->
|
||||
"""
|
||||
|
||||
root_node = astroid.parse(code)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(decorator_checker)
|
||||
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
@@ -55,7 +60,7 @@ def test_bad_callback(linter: UnittestLinter, decorator_checker: BaseChecker) ->
|
||||
end_col_offset=15,
|
||||
),
|
||||
):
|
||||
walk_checker(linter, decorator_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -105,9 +110,11 @@ def test_good_fixture(
|
||||
"""
|
||||
|
||||
root_node = astroid.parse(code, path)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(decorator_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, decorator_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -139,6 +146,8 @@ def test_bad_fixture_session_scope(
|
||||
"""
|
||||
|
||||
root_node = astroid.parse(code, path)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(decorator_checker)
|
||||
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
@@ -153,7 +162,7 @@ def test_bad_fixture_session_scope(
|
||||
end_col_offset=32,
|
||||
),
|
||||
):
|
||||
walk_checker(linter, decorator_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -184,6 +193,8 @@ def test_bad_fixture_package_scope(
|
||||
"""
|
||||
|
||||
root_node = astroid.parse(code, path)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(decorator_checker)
|
||||
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
@@ -198,7 +209,7 @@ def test_bad_fixture_package_scope(
|
||||
end_col_offset=32,
|
||||
),
|
||||
):
|
||||
walk_checker(linter, decorator_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -236,6 +247,8 @@ def test_bad_fixture_autouse(
|
||||
"""
|
||||
|
||||
root_node = astroid.parse(code, path)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(decorator_checker)
|
||||
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
@@ -250,4 +263,4 @@ def test_bad_fixture_autouse(
|
||||
end_col_offset=17 + len(keywords),
|
||||
),
|
||||
):
|
||||
walk_checker(linter, decorator_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.test_determinism import HassTestDeterminismChecker
|
||||
import pytest
|
||||
|
||||
from . import assert_no_messages, walk_checker
|
||||
from . import assert_no_messages
|
||||
|
||||
|
||||
@pytest.fixture(name="determinism_checker")
|
||||
@@ -143,9 +144,11 @@ def test_no_warning(
|
||||
) -> None:
|
||||
"""Test cases that should not trigger a warning."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(determinism_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, determinism_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_if_statement_flagged(
|
||||
@@ -164,7 +167,9 @@ def test_sensor_value(hass) -> None:
|
||||
""",
|
||||
"tests.components.test_integration.test_sensor",
|
||||
)
|
||||
walk_checker(linter, determinism_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(determinism_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -188,7 +193,9 @@ def test_something(hass) -> None:
|
||||
""",
|
||||
"tests.components.test_integration.test_init",
|
||||
)
|
||||
walk_checker(linter, determinism_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(determinism_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 2
|
||||
@@ -209,7 +216,9 @@ async def test_something(hass) -> None:
|
||||
""",
|
||||
"tests.components.test_integration.test_init",
|
||||
)
|
||||
walk_checker(linter, determinism_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(determinism_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -232,7 +241,9 @@ def test_something(state) -> None:
|
||||
""",
|
||||
"tests.components.test_integration.test_init",
|
||||
)
|
||||
walk_checker(linter, determinism_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(determinism_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.domain_constant import DomainConstantChecker
|
||||
import pytest
|
||||
|
||||
from . import assert_no_messages, walk_checker
|
||||
from . import assert_no_messages
|
||||
|
||||
|
||||
@pytest.fixture(name="domain_constant_checker")
|
||||
@@ -220,9 +221,11 @@ def test_no_warning(
|
||||
) -> None:
|
||||
"""Test cases that should not trigger a warning."""
|
||||
root_node = astroid.parse(code, "tests.components.test_integration.test_init")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(domain_constant_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, domain_constant_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -252,7 +255,9 @@ def test_domain_argument_flagged(
|
||||
) -> None:
|
||||
"""Test that non-domain arguments are flagged."""
|
||||
root_node = astroid.parse(code, "tests.components.test_integration.test_init")
|
||||
walk_checker(linter, domain_constant_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(domain_constant_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -271,6 +276,8 @@ async_setup_component(hass, OTHER, {})
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(domain_constant_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, domain_constant_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.duplicate_const import DuplicateConstChecker
|
||||
import pytest
|
||||
|
||||
from . import assert_no_messages, walk_checker
|
||||
from . import assert_no_messages
|
||||
|
||||
# Pre-load homeassistant.const so astroid can resolve it.
|
||||
astroid.MANAGER.ast_from_module_name("homeassistant.const")
|
||||
@@ -63,9 +64,11 @@ def test_no_warning(
|
||||
) -> None:
|
||||
"""Test cases that should not trigger a warning."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.test_integration.const")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(duplicate_const_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, duplicate_const_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -118,7 +121,9 @@ def test_duplicate_const_flagged(
|
||||
) -> None:
|
||||
"""Test that duplicate constants are flagged."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.test_integration.const")
|
||||
walk_checker(linter, duplicate_const_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(duplicate_const_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -137,6 +142,8 @@ CONF_HOST = "host"
|
||||
""",
|
||||
"tests.components.test_integration.test_const",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(duplicate_const_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, duplicate_const_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.entity_description_defaults import (
|
||||
EntityDescriptionDefaultsChecker,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from . import assert_no_messages, walk_checker
|
||||
from . import assert_no_messages
|
||||
|
||||
# Pre-load EntityDescription so astroid can resolve it in parsed snippets.
|
||||
# This avoids depending on component-level imports which may not be
|
||||
@@ -74,9 +75,11 @@ def test_no_warning(
|
||||
) -> None:
|
||||
"""Test cases that should not trigger a warning."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.test_integration.sensor")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(defaults_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, defaults_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -158,7 +161,9 @@ def test_redundant_default_flagged(
|
||||
) -> None:
|
||||
"""Test that redundant defaults are flagged."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.test_integration.sensor")
|
||||
walk_checker(linter, defaults_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(defaults_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -180,9 +185,11 @@ JobDescription(icon=None)
|
||||
""",
|
||||
"homeassistant.components.test_integration.sensor",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(defaults_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, defaults_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_local_entity_description_name_ignored(
|
||||
@@ -202,9 +209,11 @@ MyDescription(entity_registry_enabled_default=True)
|
||||
""",
|
||||
"homeassistant.components.test_integration.sensor",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(defaults_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, defaults_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_aliased_description_flagged(
|
||||
@@ -221,7 +230,9 @@ Alias(key="temperature", icon=None)
|
||||
""",
|
||||
"homeassistant.components.test_integration.sensor",
|
||||
)
|
||||
walk_checker(linter, defaults_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(defaults_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -245,6 +256,8 @@ EntityDescription(
|
||||
""",
|
||||
"tests.components.test_integration.test_sensor",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(defaults_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, defaults_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -6,13 +6,14 @@ from pathlib import Path
|
||||
import astroid
|
||||
from astroid import nodes
|
||||
from pylint.testutils import MessageTest, UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.entity_unique_id_format import (
|
||||
EntityUniqueIdFormatChecker,
|
||||
)
|
||||
from pylint_home_assistant.helpers.integration import clear_caches
|
||||
import pytest
|
||||
|
||||
from . import assert_adds_messages, assert_no_messages, walk_checker
|
||||
from . import assert_adds_messages, assert_no_messages
|
||||
|
||||
|
||||
@pytest.fixture(name="checker")
|
||||
@@ -180,8 +181,10 @@ def test_redundant_domain_fires(
|
||||
|
||||
root_node = _parse(code, integration_dir)
|
||||
value_node = _find_attr_value_node(root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
with assert_adds_messages(linter, _expect_redundant_domain(value_node, "MySensor")):
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_redundant_domain_fires_in_both_branches(
|
||||
@@ -220,12 +223,14 @@ class MySensor(Entity):
|
||||
)
|
||||
]
|
||||
assert len(value_nodes) == 2
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
with assert_adds_messages(
|
||||
linter,
|
||||
_expect_redundant_domain(value_nodes[0], "MySensor"),
|
||||
_expect_redundant_domain(value_nodes[1], "MySensor"),
|
||||
):
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -275,8 +280,10 @@ def test_redundant_domain_does_not_fire(
|
||||
integration_dir = _make_integration(tmp_path)
|
||||
|
||||
root_node = _parse(code, integration_dir)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -363,7 +370,9 @@ def test_redundant_domain_literal_fires(
|
||||
integration_dir = _make_integration(tmp_path, domain="myhub")
|
||||
|
||||
root_node = _parse(code, integration_dir)
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
walker.walk(root_node)
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
assert messages[0].msg_id == "home-assistant-entity-unique-id-redundant-domain"
|
||||
@@ -401,7 +410,9 @@ class MySensor(Entity):
|
||||
""",
|
||||
integration_dir,
|
||||
)
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
walker.walk(root_node)
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == (1 if fires else 0)
|
||||
|
||||
@@ -481,8 +492,10 @@ def test_redundant_domain_literal_does_not_fire_on_word_substrings(
|
||||
integration_dir = _make_integration(tmp_path, domain="myhub")
|
||||
|
||||
root_node = _parse(code, integration_dir)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -526,10 +539,12 @@ def test_redundant_domain_fires_in_unique_id_property(
|
||||
|
||||
root_node = _parse(code, integration_dir)
|
||||
return_node = next(root_node.nodes_of_class(nodes.Return))
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
with assert_adds_messages(
|
||||
linter, _expect_redundant_domain(return_node.value, "MySensor")
|
||||
):
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -576,8 +591,10 @@ def test_out_of_scope_ignored(
|
||||
root_node = _parse(
|
||||
code, integration_dir, module_name=module_name, file_name=file_name
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -623,8 +640,10 @@ class MyEntity(Entity):
|
||||
file_name=file_name,
|
||||
)
|
||||
value_node = _find_attr_value_node(root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
with assert_adds_messages(linter, _expect_redundant_domain(value_node, "MyEntity")):
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_same_module_mixin_base_fires(
|
||||
@@ -655,7 +674,9 @@ class MyConcreteSensor(MyBaseSensor):
|
||||
integration_dir,
|
||||
)
|
||||
value_node = _find_attr_value_node(root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
with assert_adds_messages(
|
||||
linter, _expect_redundant_domain(value_node, "MyBaseSensor")
|
||||
):
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -5,6 +5,7 @@ from pathlib import Path
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.exception_translations import (
|
||||
ExceptionTranslationsChecker,
|
||||
)
|
||||
@@ -13,7 +14,7 @@ from pylint_home_assistant.helpers.translations import clear_translations_cache
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from . import assert_no_messages, walk_checker
|
||||
from . import assert_no_messages
|
||||
|
||||
# Pre-load so astroid can resolve exception classes in parsed snippets.
|
||||
astroid.MANAGER.ast_from_module_name("homeassistant.exceptions")
|
||||
@@ -124,8 +125,11 @@ def test_no_warning(
|
||||
root_node = astroid.parse(code, "homeassistant.components.test_int.coordinator")
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -160,7 +164,9 @@ def test_hardcoded_string_flagged(
|
||||
root_node = astroid.parse(code, "homeassistant.components.test_int.coordinator")
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -212,7 +218,9 @@ def test_translation_key_domain_mismatch_flagged(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -241,7 +249,9 @@ raise HomeAssistantError(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -270,7 +280,9 @@ raise HomeAssistantError(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -300,8 +312,11 @@ raise HomeAssistantError(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_extra_placeholders_flagged(
|
||||
@@ -329,7 +344,9 @@ raise HomeAssistantError(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -361,7 +378,9 @@ raise HomeAssistantError(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -393,8 +412,11 @@ raise HomeAssistantError(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_placeholder_variable_resolved(
|
||||
@@ -423,8 +445,11 @@ raise HomeAssistantError(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_placeholder_variable_mismatch_flagged(
|
||||
@@ -453,7 +478,9 @@ raise HomeAssistantError(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -486,8 +513,11 @@ raise HomeAssistantError(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_constant_placeholder_keys_ok(
|
||||
@@ -516,8 +546,11 @@ raise HomeAssistantError(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_key_reference_resolution(
|
||||
@@ -555,8 +588,11 @@ raise HomeAssistantError(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_no_strings_json_flags_missing_key(
|
||||
@@ -578,7 +614,9 @@ raise HomeAssistantError(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -609,7 +647,9 @@ raise HomeAssistantError(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -642,7 +682,9 @@ raise HomeAssistantError(
|
||||
)
|
||||
root_node.file = str(integration_dir / "coordinator.py")
|
||||
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -658,6 +700,8 @@ def test_not_integration_ignored(
|
||||
f'{_HA_IMPORTS}\nraise HomeAssistantError("hardcoded")',
|
||||
"tests.components.test_integration",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(translations_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, translations_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
import astroid
|
||||
from pylint.checkers import BaseChecker
|
||||
from pylint.testutils.unittest_linter import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
import pytest
|
||||
|
||||
from . import assert_no_messages, walk_checker
|
||||
from . import assert_no_messages
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -76,9 +77,11 @@ def test_enforce_greek_micro_char(
|
||||
) -> None:
|
||||
"""Good test cases."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.pylint_test")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_greek_micro_char_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_greek_micro_char_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -150,8 +153,10 @@ def test_enforce_greek_micro_char_assign_bad(
|
||||
) -> None:
|
||||
"""Bad assignment test cases."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.pylint_test")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_greek_micro_char_checker)
|
||||
|
||||
walk_checker(linter, enforce_greek_micro_char_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
message = next(iter(messages))
|
||||
|
||||
@@ -5,11 +5,12 @@ from pathlib import Path
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.mdi_icons import MdiIconsChecker
|
||||
from pylint_home_assistant.helpers.icons import clear_icons_cache
|
||||
import pytest
|
||||
|
||||
from . import assert_no_messages, walk_checker
|
||||
from . import assert_no_messages
|
||||
|
||||
|
||||
@pytest.fixture(name="mdi_checker")
|
||||
@@ -77,9 +78,11 @@ def test_python_no_warning(
|
||||
) -> None:
|
||||
"""Test that valid MDI icons in Python code pass."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.test_integration.sensor")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(mdi_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, mdi_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -115,7 +118,9 @@ def test_python_invalid_icon_flagged(
|
||||
) -> None:
|
||||
"""Test that invalid MDI icons in Python code are flagged."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.test_integration.sensor")
|
||||
walk_checker(linter, mdi_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(mdi_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -132,9 +137,11 @@ def test_python_not_integration_ignored(
|
||||
'ICON = "mdi:nonexistent-icon"',
|
||||
"tests.components.test_integration",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(mdi_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, mdi_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
# --- icons.json tests ---
|
||||
@@ -166,8 +173,11 @@ def test_icons_json_valid(
|
||||
)
|
||||
root_node.file = str(integration_dir / "__init__.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(mdi_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, mdi_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_icons_json_invalid_flagged(
|
||||
@@ -193,7 +203,9 @@ def test_icons_json_invalid_flagged(
|
||||
)
|
||||
root_node.file = str(integration_dir / "__init__.py")
|
||||
|
||||
walk_checker(linter, mdi_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(mdi_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -215,8 +227,11 @@ def test_icons_json_no_file_no_warning(
|
||||
)
|
||||
root_node.file = str(integration_dir / "__init__.py")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(mdi_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, mdi_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_icons_json_nested_invalid_flagged(
|
||||
@@ -250,7 +265,9 @@ def test_icons_json_nested_invalid_flagged(
|
||||
)
|
||||
root_node.file = str(integration_dir / "__init__.py")
|
||||
|
||||
walk_checker(linter, mdi_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(mdi_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
import astroid
|
||||
from pylint.checkers import BaseChecker
|
||||
from pylint.testutils.unittest_linter import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
import pytest
|
||||
|
||||
from . import assert_no_messages, walk_checker
|
||||
from . import assert_no_messages
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -70,9 +71,11 @@ def test_enforce_naive_now_good(
|
||||
) -> None:
|
||||
"""Good test cases -- no message expected."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.pylint_test")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_naive_now_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_naive_now_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -155,8 +158,10 @@ def test_enforce_naive_now_bad(
|
||||
) -> None:
|
||||
"""Bad test cases -- one message expected per call."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.pylint_test")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_naive_now_checker)
|
||||
|
||||
walk_checker(linter, enforce_naive_now_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
assert messages[0].msg_id == "home-assistant-enforce-naive-now"
|
||||
@@ -192,6 +197,8 @@ def test_enforce_naive_now_skips_util_dt(
|
||||
) -> None:
|
||||
"""``homeassistant.util.dt`` defines ``naive_now`` itself, so it is skipped."""
|
||||
root_node = astroid.parse(code, "homeassistant.util.dt")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_naive_now_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_naive_now_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
import astroid
|
||||
from pylint.checkers import BaseChecker
|
||||
from pylint.testutils.unittest_linter import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
import pytest
|
||||
|
||||
from . import assert_no_messages, walk_checker
|
||||
from . import assert_no_messages
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -135,9 +136,11 @@ def test_enforce_now_good(
|
||||
) -> None:
|
||||
"""Good test cases -- no message expected."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.pylint_test")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_now_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_now_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -227,8 +230,10 @@ def test_enforce_now_bad(
|
||||
) -> None:
|
||||
"""Bad test cases -- one message expected per call."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.pylint_test")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_now_checker)
|
||||
|
||||
walk_checker(linter, enforce_now_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
assert messages[0].msg_id == "home-assistant-enforce-now"
|
||||
@@ -265,6 +270,8 @@ def test_enforce_now_skips_util_dt(
|
||||
) -> None:
|
||||
"""``homeassistant.util.dt`` defines ``now`` itself, so it is skipped."""
|
||||
root_node = astroid.parse(code, "homeassistant.util.dt")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_now_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_now_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -5,9 +5,10 @@ from pathlib import Path
|
||||
import astroid
|
||||
from pylint.checkers import BaseChecker
|
||||
from pylint.testutils.unittest_linter import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
import pytest
|
||||
|
||||
from . import assert_no_messages, walk_checker
|
||||
from . import assert_no_messages
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -103,9 +104,11 @@ def test_enforce_runtime_data(
|
||||
) -> None:
|
||||
"""Good test cases."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_runtime_data_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_runtime_data_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -157,8 +160,10 @@ def test_enforce_runtime_data_bad(
|
||||
) -> None:
|
||||
"""Bad test cases."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_runtime_data_checker)
|
||||
|
||||
walk_checker(linter, enforce_runtime_data_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
assert messages[0].msg_id == "home-assistant-use-runtime-data"
|
||||
@@ -182,8 +187,11 @@ def test_enforce_runtime_data_no_config_flow(
|
||||
root_node = astroid.parse(code, "homeassistant.components.yaml_only")
|
||||
root_node.file = str(init_file)
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_runtime_data_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_runtime_data_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_enforce_runtime_data_with_config_flow(
|
||||
@@ -205,7 +213,10 @@ def test_enforce_runtime_data_with_config_flow(
|
||||
root_node = astroid.parse(code, "homeassistant.components.modern")
|
||||
root_node.file = str(init_file)
|
||||
|
||||
walk_checker(linter, enforce_runtime_data_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_runtime_data_checker)
|
||||
|
||||
walker.walk(root_node)
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
assert messages[0].msg_id == "home-assistant-use-runtime-data"
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.sequential_executor_jobs import (
|
||||
SequentialExecutorJobsChecker,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from . import assert_no_messages, walk_checker
|
||||
from . import assert_no_messages
|
||||
|
||||
|
||||
@pytest.fixture(name="executor_checker")
|
||||
@@ -55,9 +56,11 @@ def test_no_warning(
|
||||
) -> None:
|
||||
"""Test cases that should not trigger a warning."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.test_integration")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(executor_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, executor_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_two_sequential_flagged(
|
||||
@@ -73,7 +76,9 @@ async def async_setup(hass, config):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walk_checker(linter, executor_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(executor_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -94,7 +99,9 @@ async def async_setup(hass, config):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walk_checker(linter, executor_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(executor_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 2
|
||||
@@ -113,7 +120,9 @@ async def async_setup(hass, config):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walk_checker(linter, executor_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(executor_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -135,7 +144,9 @@ async def async_setup(hass, config):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walk_checker(linter, executor_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(executor_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -157,7 +168,9 @@ async def async_setup(hass, config):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walk_checker(linter, executor_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(executor_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -180,7 +193,9 @@ async def async_setup(hass, config):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walk_checker(linter, executor_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(executor_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -203,7 +218,9 @@ async def async_setup(hass, config):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walk_checker(linter, executor_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(executor_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -223,7 +240,9 @@ async def async_setup(hass, config):
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walk_checker(linter, executor_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(executor_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -243,6 +262,8 @@ async def async_setup(hass, config):
|
||||
""",
|
||||
"tests.components.test_integration",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(executor_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, executor_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -5,9 +5,10 @@ from pylint.checkers import BaseChecker
|
||||
from pylint.interfaces import UNDEFINED
|
||||
from pylint.testutils import MessageTest
|
||||
from pylint.testutils.unittest_linter import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
import pytest
|
||||
|
||||
from . import assert_adds_messages, assert_no_messages, walk_checker
|
||||
from . import assert_adds_messages, assert_no_messages
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -74,9 +75,11 @@ def test_enforce_sorted_platforms(
|
||||
) -> None:
|
||||
"""Good test cases."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.pylint_test")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_sorted_platforms_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_sorted_platforms_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_enforce_sorted_platforms_bad(
|
||||
|
||||
@@ -7,9 +7,10 @@ from pylint.checkers import BaseChecker
|
||||
from pylint.interfaces import INFERENCE
|
||||
from pylint.testutils import MessageTest
|
||||
from pylint.testutils.unittest_linter import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
import pytest
|
||||
|
||||
from . import assert_adds_messages, assert_no_messages, walk_checker
|
||||
from . import assert_adds_messages, assert_no_messages
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -116,6 +117,8 @@ def test_enforce_super_call(
|
||||
) -> None:
|
||||
"""Good test cases."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.pylint_test")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(super_call_checker)
|
||||
|
||||
with (
|
||||
patch(
|
||||
@@ -124,7 +127,7 @@ def test_enforce_super_call(
|
||||
),
|
||||
assert_no_messages(linter),
|
||||
):
|
||||
walk_checker(linter, super_call_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -196,6 +199,8 @@ def test_enforce_super_call_bad(
|
||||
) -> None:
|
||||
"""Bad test cases."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.pylint_test")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(super_call_checker)
|
||||
node = root_node.body[node_idx].body[0]
|
||||
|
||||
with (
|
||||
@@ -217,4 +222,4 @@ def test_enforce_super_call_bad(
|
||||
),
|
||||
),
|
||||
):
|
||||
walk_checker(linter, super_call_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.unnecessary_format_mac import (
|
||||
UnnecessaryFormatMacChecker,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from . import assert_no_messages, walk_checker
|
||||
from . import assert_no_messages
|
||||
|
||||
_IMPORTS = """\
|
||||
from homeassistant.helpers.device_registry import (
|
||||
@@ -94,8 +95,11 @@ def test_no_warning(
|
||||
"""Test cases that should not trigger a warning."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.test_integration.sensor")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(format_mac_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, format_mac_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -148,7 +152,9 @@ def test_format_mac_flagged(
|
||||
"""Warning when format_mac is used in connections= keyword argument."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.test_integration.sensor")
|
||||
|
||||
walk_checker(linter, format_mac_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(format_mac_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -168,5 +174,8 @@ device = DeviceInfo(
|
||||
"""
|
||||
root_node = astroid.parse(code, "some_other.module")
|
||||
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(format_mac_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, format_mac_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.unused_test_fixture_args import (
|
||||
UnusedTestFixtureArgsChecker,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from . import assert_no_messages, walk_checker
|
||||
from . import assert_no_messages
|
||||
|
||||
|
||||
@pytest.fixture(name="unused_args_checker")
|
||||
@@ -67,9 +68,11 @@ def test_no_warning(
|
||||
) -> None:
|
||||
"""Test cases that should not trigger a warning."""
|
||||
root_node = astroid.parse(code, "tests.components.test_integration.test_init")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(unused_args_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, unused_args_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_unused_single_arg(
|
||||
@@ -84,7 +87,9 @@ def test_something(hass: HomeAssistant, enable_bluetooth: None) -> None:
|
||||
""",
|
||||
"tests.components.test_integration.test_init",
|
||||
)
|
||||
walk_checker(linter, unused_args_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(unused_args_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -110,7 +115,9 @@ def test_something(
|
||||
""",
|
||||
"tests.components.test_integration.test_init",
|
||||
)
|
||||
walk_checker(linter, unused_args_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(unused_args_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 2
|
||||
@@ -130,9 +137,11 @@ def test_something(unused: str) -> None:
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(unused_args_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, unused_args_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_async_test_function(
|
||||
@@ -147,7 +156,9 @@ async def test_something(hass: HomeAssistant, enable_bluetooth: None) -> None:
|
||||
""",
|
||||
"tests.components.test_integration.test_init",
|
||||
)
|
||||
walk_checker(linter, unused_args_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(unused_args_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
import astroid
|
||||
from pylint.checkers import BaseChecker
|
||||
from pylint.testutils.unittest_linter import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
import pytest
|
||||
|
||||
from . import assert_no_messages, walk_checker
|
||||
from . import assert_no_messages
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -71,9 +72,11 @@ def test_enforce_utcnow_good(
|
||||
) -> None:
|
||||
"""Good test cases -- no message expected."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.pylint_test")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_utcnow_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_utcnow_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -240,8 +243,10 @@ def test_enforce_utcnow_bad(
|
||||
) -> None:
|
||||
"""Bad test cases -- one message expected per call."""
|
||||
root_node = astroid.parse(code, "homeassistant.components.pylint_test")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_utcnow_checker)
|
||||
|
||||
walk_checker(linter, enforce_utcnow_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
assert messages[0].msg_id == "home-assistant-enforce-utcnow"
|
||||
@@ -277,6 +282,8 @@ def test_enforce_utcnow_skips_util_dt(
|
||||
) -> None:
|
||||
"""``homeassistant.util.dt`` defines ``utcnow`` itself, so it is skipped."""
|
||||
root_node = astroid.parse(code, "homeassistant.util.dt")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_utcnow_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, enforce_utcnow_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.tests.direct_async_migrate_entry import (
|
||||
DirectAsyncMigrateEntry,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from tests.pylint import assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_no_messages
|
||||
|
||||
# Pre-load so astroid can resolve ``async_migrate_entry`` in parsed snippets.
|
||||
astroid.MANAGER.ast_from_module_name("homeassistant.components.ps4")
|
||||
@@ -69,9 +70,11 @@ def test_no_warning(
|
||||
) -> None:
|
||||
"""Test cases that should not trigger a warning."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -107,7 +110,9 @@ def test_warning(
|
||||
) -> None:
|
||||
"""Test cases that should trigger a warning."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -131,7 +136,9 @@ async def test_b(hass, mock_config_entry):
|
||||
""",
|
||||
"tests.components.ps4.test_init",
|
||||
)
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 2
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.tests.direct_async_setup import DirectAsyncSetup
|
||||
import pytest
|
||||
|
||||
from tests.pylint import assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_no_messages
|
||||
|
||||
# Pre-load so astroid can resolve ``async_setup`` in parsed snippets.
|
||||
astroid.MANAGER.ast_from_module_name("homeassistant.components.ps4")
|
||||
@@ -77,9 +78,11 @@ def test_no_warning(
|
||||
) -> None:
|
||||
"""Test cases that should not trigger a warning."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -115,7 +118,9 @@ def test_warning(
|
||||
) -> None:
|
||||
"""Test cases that should trigger a warning."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -139,7 +144,9 @@ async def test_b(hass):
|
||||
""",
|
||||
"tests.components.ps4.test_init",
|
||||
)
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 2
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.tests.direct_async_setup_entry import (
|
||||
DirectAsyncSetupEntry,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from tests.pylint import assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_no_messages
|
||||
|
||||
# Pre-load so astroid can resolve ``async_setup_entry`` in parsed snippets.
|
||||
astroid.MANAGER.ast_from_module_name("homeassistant.components.sun")
|
||||
@@ -70,9 +71,11 @@ def test_no_warning(
|
||||
) -> None:
|
||||
"""Test cases that should not trigger a warning."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -133,7 +136,9 @@ def test_warning(
|
||||
) -> None:
|
||||
"""Test cases that should trigger a warning."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -157,7 +162,9 @@ async def test_b(hass, mock_config_entry):
|
||||
""",
|
||||
"tests.components.sun.test_init",
|
||||
)
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 2
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.tests.direct_async_unload_entry import (
|
||||
DirectAsyncUnloadEntry,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from tests.pylint import assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_no_messages
|
||||
|
||||
# Pre-load so astroid can resolve ``async_unload_entry`` in parsed snippets.
|
||||
astroid.MANAGER.ast_from_module_name("homeassistant.components.ps4")
|
||||
@@ -69,9 +70,11 @@ def test_no_warning(
|
||||
) -> None:
|
||||
"""Test cases that should not trigger a warning."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -107,7 +110,9 @@ def test_warning(
|
||||
) -> None:
|
||||
"""Test cases that should trigger a warning."""
|
||||
root_node = astroid.parse(code, module_name)
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -131,7 +136,9 @@ async def test_b(hass, mock_config_entry):
|
||||
""",
|
||||
"tests.components.ps4.test_init",
|
||||
)
|
||||
walk_checker(linter, checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 2
|
||||
|
||||
@@ -4,12 +4,13 @@ from pathlib import Path
|
||||
|
||||
import astroid
|
||||
from pylint.testutils import UnittestLinter
|
||||
from pylint.utils.ast_walker import ASTWalker
|
||||
from pylint_home_assistant.checkers.tests.redundant_usefixtures import (
|
||||
RedundantUsefixtures,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from tests.pylint import assert_no_messages, walk_checker
|
||||
from tests.pylint import assert_no_messages
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -72,9 +73,11 @@ def test_no_warning(
|
||||
) -> None:
|
||||
"""Test cases that should not trigger a warning."""
|
||||
root_node = astroid.parse(code, "tests.components.test_integration.test_init")
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(usefixtures_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, usefixtures_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
def test_single_fixture_redundant(
|
||||
@@ -92,7 +95,9 @@ async def test_something(hass: HomeAssistant) -> None:
|
||||
""",
|
||||
"tests.components.test_integration.test_init",
|
||||
)
|
||||
walk_checker(linter, usefixtures_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(usefixtures_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -115,7 +120,9 @@ async def test_something(hass: HomeAssistant) -> None:
|
||||
""",
|
||||
"tests.components.test_integration.test_init",
|
||||
)
|
||||
walk_checker(linter, usefixtures_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(usefixtures_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -137,7 +144,9 @@ async def test_something(hass: HomeAssistant) -> None:
|
||||
""",
|
||||
"tests.components.test_integration.test_init",
|
||||
)
|
||||
walk_checker(linter, usefixtures_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(usefixtures_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -162,7 +171,9 @@ async def test_b(hass: HomeAssistant) -> None:
|
||||
""",
|
||||
"tests.components.test_integration.test_init",
|
||||
)
|
||||
walk_checker(linter, usefixtures_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(usefixtures_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 2
|
||||
@@ -191,7 +202,9 @@ async def test_something(hass: HomeAssistant) -> None:
|
||||
)
|
||||
root_node.file = str(test_dir / "test_init.py")
|
||||
|
||||
walk_checker(linter, usefixtures_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(usefixtures_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -222,7 +235,9 @@ async def test_something(hass: HomeAssistant) -> None:
|
||||
)
|
||||
root_node.file = str(test_dir / "test_init.py")
|
||||
|
||||
walk_checker(linter, usefixtures_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(usefixtures_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -253,7 +268,9 @@ async def test_something(hass: HomeAssistant) -> None:
|
||||
)
|
||||
root_node.file = str(test_dir / "test_init.py")
|
||||
|
||||
walk_checker(linter, usefixtures_checker, root_node)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(usefixtures_checker)
|
||||
walker.walk(root_node)
|
||||
|
||||
messages = linter.release_messages()
|
||||
assert len(messages) == 1
|
||||
@@ -275,6 +292,8 @@ async def test_something(hass: HomeAssistant) -> None:
|
||||
""",
|
||||
"homeassistant.components.test_integration",
|
||||
)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(usefixtures_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walk_checker(linter, usefixtures_checker, root_node)
|
||||
walker.walk(root_node)
|
||||
|
||||
Reference in New Issue
Block a user