mirror of
https://github.com/home-assistant/core.git
synced 2026-09-30 01:34:36 +01:00
190 lines
6.4 KiB
Python
190 lines
6.4 KiB
Python
"""Tests for the Sofar link tuning."""
|
|
|
|
from datetime import timedelta
|
|
|
|
from freezegun.api import FrozenDateTimeFactory
|
|
from modbus_connection import (
|
|
ModbusConnectionError,
|
|
ModbusTimeoutError,
|
|
ServerDeviceBusyError,
|
|
)
|
|
from modbus_connection.mock import MockModbusConnection
|
|
import pytest
|
|
from sofar_modbus.model import UpdateReport
|
|
|
|
from homeassistant.components.sofar.const import SCAN_INTERVAL
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
|
from tests.components.diagnostics import get_diagnostics_for_config_entry
|
|
from tests.typing import ClientSessionGenerator
|
|
|
|
GRID_REGISTER = 0x0484
|
|
|
|
CLEAN_POLLS = 5
|
|
"""Polls the tuner wants before it acts on what it measured."""
|
|
|
|
EARNED_TIMEOUT = 0.5
|
|
"""The shortest ask the tuner makes, which a mocked link always earns."""
|
|
|
|
|
|
async def _poll(
|
|
hass: HomeAssistant, freezer: FrozenDateTimeFactory, count: int
|
|
) -> None:
|
|
"""Run the readings coordinator ``count`` times."""
|
|
for _ in range(count):
|
|
freezer.tick(timedelta(seconds=SCAN_INTERVAL))
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
@pytest.mark.usefixtures("init_integration")
|
|
async def test_tuner_lowers_the_link_timeout(
|
|
hass: HomeAssistant,
|
|
freezer: FrozenDateTimeFactory,
|
|
mock_connection: MockModbusConnection,
|
|
) -> None:
|
|
"""Test a link that answers cleanly is asked for a shorter timeout."""
|
|
unit = mock_connection.for_unit(1)
|
|
assert unit.required_timeout is None
|
|
|
|
await _poll(hass, freezer, CLEAN_POLLS)
|
|
|
|
assert unit.required_timeout == EARNED_TIMEOUT
|
|
|
|
|
|
@pytest.mark.usefixtures("init_integration")
|
|
async def test_tuner_withdraws_the_ask_after_a_timeout(
|
|
hass: HomeAssistant,
|
|
freezer: FrozenDateTimeFactory,
|
|
mock_connection: MockModbusConnection,
|
|
) -> None:
|
|
"""Test a timed-out poll hands the link its own timeout back."""
|
|
unit = mock_connection.for_unit(1)
|
|
await _poll(hass, freezer, CLEAN_POLLS)
|
|
assert unit.required_timeout == EARNED_TIMEOUT
|
|
|
|
unit.fail_read(GRID_REGISTER, ModbusTimeoutError("stuck"))
|
|
await _poll(hass, freezer, 1)
|
|
|
|
assert unit.required_timeout is None
|
|
|
|
|
|
@pytest.mark.usefixtures("init_integration")
|
|
async def test_tuner_withdraws_the_ask_when_the_poll_raises(
|
|
hass: HomeAssistant,
|
|
freezer: FrozenDateTimeFactory,
|
|
mock_connection: MockModbusConnection,
|
|
) -> None:
|
|
"""Test a poll that times out before anything answers is still heard."""
|
|
unit = mock_connection.for_unit(1)
|
|
await _poll(hass, freezer, CLEAN_POLLS)
|
|
assert unit.required_timeout == EARNED_TIMEOUT
|
|
|
|
unit.fail_requests(ModbusTimeoutError("link gone slow"))
|
|
await _poll(hass, freezer, 1)
|
|
|
|
assert unit.required_timeout is None
|
|
|
|
|
|
async def test_tuner_hears_a_timeout_only_the_retry_hit(
|
|
hass: HomeAssistant,
|
|
freezer: FrozenDateTimeFactory,
|
|
mock_connection: MockModbusConnection,
|
|
init_integration: MockConfigEntry,
|
|
) -> None:
|
|
"""Test a component that times out only on the retry still withdraws it."""
|
|
unit = mock_connection.for_unit(1)
|
|
await _poll(hass, freezer, CLEAN_POLLS)
|
|
assert unit.required_timeout == EARNED_TIMEOUT
|
|
|
|
async def busy_grid() -> UpdateReport:
|
|
"""A first attempt that failed without timing out."""
|
|
return UpdateReport({"state"}, {"grid": ServerDeviceBusyError("busy")})
|
|
|
|
init_integration.runtime_data.readings._poll = busy_grid
|
|
unit.fail_read(GRID_REGISTER, ModbusTimeoutError("stuck on retry"))
|
|
await _poll(hass, freezer, 1)
|
|
|
|
assert unit.required_timeout is None
|
|
|
|
|
|
async def test_tuner_hears_a_timeout_the_retry_recovered(
|
|
hass: HomeAssistant,
|
|
freezer: FrozenDateTimeFactory,
|
|
mock_connection: MockModbusConnection,
|
|
init_integration: MockConfigEntry,
|
|
) -> None:
|
|
"""Test a timeout that answered on the second attempt still withdraws it."""
|
|
unit = mock_connection.for_unit(1)
|
|
await _poll(hass, freezer, CLEAN_POLLS)
|
|
assert unit.required_timeout == EARNED_TIMEOUT
|
|
|
|
async def timed_out_grid() -> UpdateReport:
|
|
"""A first attempt that timed out; the retry finds the unit healthy."""
|
|
return UpdateReport({"state"}, {"grid": ModbusTimeoutError("slow")})
|
|
|
|
init_integration.runtime_data.readings._poll = timed_out_grid
|
|
await _poll(hass, freezer, 1)
|
|
|
|
assert unit.required_timeout is None
|
|
|
|
|
|
async def test_tuner_hears_a_timeout_the_retry_reported_otherwise(
|
|
hass: HomeAssistant,
|
|
freezer: FrozenDateTimeFactory,
|
|
mock_connection: MockModbusConnection,
|
|
init_integration: MockConfigEntry,
|
|
) -> None:
|
|
"""Test a retry's own error does not bury the timeout that preceded it."""
|
|
unit = mock_connection.for_unit(1)
|
|
await _poll(hass, freezer, CLEAN_POLLS)
|
|
assert unit.required_timeout == EARNED_TIMEOUT
|
|
|
|
async def timed_out_grid() -> UpdateReport:
|
|
"""A first attempt that timed out."""
|
|
return UpdateReport({"state"}, {"grid": ModbusTimeoutError("slow")})
|
|
|
|
init_integration.runtime_data.readings._poll = timed_out_grid
|
|
unit.fail_read(GRID_REGISTER, ServerDeviceBusyError("busy on retry"))
|
|
await _poll(hass, freezer, 1)
|
|
|
|
assert unit.required_timeout is None
|
|
|
|
|
|
async def test_tuner_hears_a_timeout_when_the_retry_loses_the_link(
|
|
hass: HomeAssistant,
|
|
freezer: FrozenDateTimeFactory,
|
|
mock_connection: MockModbusConnection,
|
|
init_integration: MockConfigEntry,
|
|
) -> None:
|
|
"""Test a link dying during the retry does not bury the earlier timeout."""
|
|
unit = mock_connection.for_unit(1)
|
|
await _poll(hass, freezer, CLEAN_POLLS)
|
|
assert unit.required_timeout == EARNED_TIMEOUT
|
|
|
|
async def timed_out_grid() -> UpdateReport:
|
|
"""A first attempt that timed out."""
|
|
return UpdateReport({"state"}, {"grid": ModbusTimeoutError("slow")})
|
|
|
|
init_integration.runtime_data.readings._poll = timed_out_grid
|
|
unit.fail_read(GRID_REGISTER, ModbusConnectionError("link gone"))
|
|
await _poll(hass, freezer, 1)
|
|
|
|
assert unit.required_timeout is None
|
|
|
|
|
|
async def test_diagnostics_report_what_the_tuner_asked(
|
|
hass: HomeAssistant,
|
|
hass_client: ClientSessionGenerator,
|
|
freezer: FrozenDateTimeFactory,
|
|
mock_connection: MockModbusConnection,
|
|
init_integration: MockConfigEntry,
|
|
) -> None:
|
|
"""Test the tuning a link settled on reaches the diagnostics dump."""
|
|
await _poll(hass, freezer, CLEAN_POLLS)
|
|
|
|
diag = await get_diagnostics_for_config_entry(hass, hass_client, init_integration)
|
|
|
|
assert diag["link"]["tuning"]["timeout"] == EARNED_TIMEOUT
|