mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 07:07:28 +00:00
Add tests for System Monitor (#107891)
* Add tests * no coordinator * Coverage * processes * test init * util * test icon * Add tests * Mod tests * Add tests * Test attributes * snapshots * icon * test disk mounts * fixes * svmem * cache_clear * test icon * reset icon test * test_processor_temperature * fix tests on macos --------- Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
749ef45727
commit
5b3e1306f8
@ -1316,9 +1316,6 @@ omit =
|
|||||||
homeassistant/components/system_bridge/notify.py
|
homeassistant/components/system_bridge/notify.py
|
||||||
homeassistant/components/system_bridge/sensor.py
|
homeassistant/components/system_bridge/sensor.py
|
||||||
homeassistant/components/system_bridge/update.py
|
homeassistant/components/system_bridge/update.py
|
||||||
homeassistant/components/systemmonitor/__init__.py
|
|
||||||
homeassistant/components/systemmonitor/sensor.py
|
|
||||||
homeassistant/components/systemmonitor/util.py
|
|
||||||
homeassistant/components/tado/__init__.py
|
homeassistant/components/tado/__init__.py
|
||||||
homeassistant/components/tado/binary_sensor.py
|
homeassistant/components/tado/binary_sensor.py
|
||||||
homeassistant/components/tado/climate.py
|
homeassistant/components/tado/climate.py
|
||||||
|
@ -4,12 +4,12 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from functools import cache
|
from functools import cache, lru_cache
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
from typing import Any
|
from typing import Any, Literal
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -56,10 +56,6 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
CONF_ARG = "arg"
|
CONF_ARG = "arg"
|
||||||
|
|
||||||
if sys.maxsize > 2**32:
|
|
||||||
CPU_ICON = "mdi:cpu-64-bit"
|
|
||||||
else:
|
|
||||||
CPU_ICON = "mdi:cpu-32-bit"
|
|
||||||
|
|
||||||
SENSOR_TYPE_NAME = 0
|
SENSOR_TYPE_NAME = 0
|
||||||
SENSOR_TYPE_UOM = 1
|
SENSOR_TYPE_UOM = 1
|
||||||
@ -70,6 +66,14 @@ SENSOR_TYPE_MANDATORY_ARG = 4
|
|||||||
SIGNAL_SYSTEMMONITOR_UPDATE = "systemmonitor_update"
|
SIGNAL_SYSTEMMONITOR_UPDATE = "systemmonitor_update"
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def get_cpu_icon() -> Literal["mdi:cpu-64-bit", "mdi:cpu-32-bit"]:
|
||||||
|
"""Return cpu icon."""
|
||||||
|
if sys.maxsize > 2**32:
|
||||||
|
return "mdi:cpu-64-bit"
|
||||||
|
return "mdi:cpu-32-bit"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class SysMonitorSensorEntityDescription(SensorEntityDescription):
|
class SysMonitorSensorEntityDescription(SensorEntityDescription):
|
||||||
"""Description for System Monitor sensor entities."""
|
"""Description for System Monitor sensor entities."""
|
||||||
@ -121,19 +125,19 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = {
|
|||||||
"load_15m": SysMonitorSensorEntityDescription(
|
"load_15m": SysMonitorSensorEntityDescription(
|
||||||
key="load_15m",
|
key="load_15m",
|
||||||
name="Load (15m)",
|
name="Load (15m)",
|
||||||
icon=CPU_ICON,
|
icon=get_cpu_icon(),
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
"load_1m": SysMonitorSensorEntityDescription(
|
"load_1m": SysMonitorSensorEntityDescription(
|
||||||
key="load_1m",
|
key="load_1m",
|
||||||
name="Load (1m)",
|
name="Load (1m)",
|
||||||
icon=CPU_ICON,
|
icon=get_cpu_icon(),
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
"load_5m": SysMonitorSensorEntityDescription(
|
"load_5m": SysMonitorSensorEntityDescription(
|
||||||
key="load_5m",
|
key="load_5m",
|
||||||
name="Load (5m)",
|
name="Load (5m)",
|
||||||
icon=CPU_ICON,
|
icon=get_cpu_icon(),
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
"memory_free": SysMonitorSensorEntityDescription(
|
"memory_free": SysMonitorSensorEntityDescription(
|
||||||
@ -210,14 +214,14 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = {
|
|||||||
"process": SysMonitorSensorEntityDescription(
|
"process": SysMonitorSensorEntityDescription(
|
||||||
key="process",
|
key="process",
|
||||||
name="Process",
|
name="Process",
|
||||||
icon=CPU_ICON,
|
icon=get_cpu_icon(),
|
||||||
mandatory_arg=True,
|
mandatory_arg=True,
|
||||||
),
|
),
|
||||||
"processor_use": SysMonitorSensorEntityDescription(
|
"processor_use": SysMonitorSensorEntityDescription(
|
||||||
key="processor_use",
|
key="processor_use",
|
||||||
name="Processor use",
|
name="Processor use",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon=CPU_ICON,
|
icon=get_cpu_icon(),
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
"processor_temperature": SysMonitorSensorEntityDescription(
|
"processor_temperature": SysMonitorSensorEntityDescription(
|
||||||
@ -751,7 +755,11 @@ def _getloadavg() -> tuple[float, float, float]:
|
|||||||
|
|
||||||
def _read_cpu_temperature() -> float | None:
|
def _read_cpu_temperature() -> float | None:
|
||||||
"""Attempt to read CPU / processor temperature."""
|
"""Attempt to read CPU / processor temperature."""
|
||||||
|
try:
|
||||||
temps = psutil.sensors_temperatures()
|
temps = psutil.sensors_temperatures()
|
||||||
|
except AttributeError:
|
||||||
|
# Linux, macOS
|
||||||
|
return None
|
||||||
|
|
||||||
for name, entries in temps.items():
|
for name, entries in temps.items():
|
||||||
for i, entry in enumerate(entries, start=1):
|
for i, entry in enumerate(entries, start=1):
|
||||||
|
@ -1,11 +1,61 @@
|
|||||||
"""Fixtures for the System Monitor integration."""
|
"""Fixtures for the System Monitor integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from unittest.mock import AsyncMock, patch
|
import socket
|
||||||
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
|
from psutil import NoSuchProcess, Process
|
||||||
|
from psutil._common import sdiskpart, sdiskusage, shwtemp, snetio, snicaddr, sswap
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.systemmonitor.const import DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
# Different depending on platform so making according to Linux
|
||||||
|
svmem = namedtuple(
|
||||||
|
"svmem",
|
||||||
|
[
|
||||||
|
"total",
|
||||||
|
"available",
|
||||||
|
"percent",
|
||||||
|
"used",
|
||||||
|
"free",
|
||||||
|
"active",
|
||||||
|
"inactive",
|
||||||
|
"buffers",
|
||||||
|
"cached",
|
||||||
|
"shared",
|
||||||
|
"slab",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_sys_platform() -> Generator[None, None, None]:
|
||||||
|
"""Mock sys platform to Linux."""
|
||||||
|
with patch("sys.platform", "linux"):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
class MockProcess(Process):
|
||||||
|
"""Mock a Process class."""
|
||||||
|
|
||||||
|
def __init__(self, name: str, ex: bool = False) -> None:
|
||||||
|
"""Initialize the process."""
|
||||||
|
super().__init__(1)
|
||||||
|
self._name = name
|
||||||
|
self._ex = ex
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
"""Return a name."""
|
||||||
|
if self._ex:
|
||||||
|
raise NoSuchProcess(1, self._name)
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||||
@ -15,3 +65,184 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
|||||||
return_value=True,
|
return_value=True,
|
||||||
) as mock_setup_entry:
|
) as mock_setup_entry:
|
||||||
yield mock_setup_entry
|
yield mock_setup_entry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_config_entry() -> MockConfigEntry:
|
||||||
|
"""Mock ConfigEntry."""
|
||||||
|
return MockConfigEntry(
|
||||||
|
title="System Monitor",
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={},
|
||||||
|
options={
|
||||||
|
"sensor": {"process": ["python3", "pip"]},
|
||||||
|
"resources": [
|
||||||
|
"disk_use_percent_/",
|
||||||
|
"disk_use_percent_/home/notexist/",
|
||||||
|
"memory_free_",
|
||||||
|
"network_out_eth0",
|
||||||
|
"process_python3",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def mock_added_config_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_psutil: Mock,
|
||||||
|
mock_os: Mock,
|
||||||
|
mock_util: Mock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> MockConfigEntry:
|
||||||
|
"""Mock ConfigEntry that's been added to HA."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert DOMAIN in hass.config_entries.async_domains()
|
||||||
|
return mock_config_entry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_process() -> list[MockProcess]:
|
||||||
|
"""Mock process."""
|
||||||
|
_process_python = MockProcess("python3")
|
||||||
|
_process_pip = MockProcess("pip")
|
||||||
|
return [_process_python, _process_pip]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_psutil(mock_process: list[MockProcess]) -> Mock:
|
||||||
|
"""Mock psutil."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.systemmonitor.sensor.psutil",
|
||||||
|
autospec=True,
|
||||||
|
) as mock_psutil:
|
||||||
|
mock_psutil.disk_usage.return_value = sdiskusage(
|
||||||
|
500 * 1024**2, 300 * 1024**2, 200 * 1024**2, 60.0
|
||||||
|
)
|
||||||
|
mock_psutil.swap_memory.return_value = sswap(
|
||||||
|
100 * 1024**2, 60 * 1024**2, 40 * 1024**2, 60.0, 1, 1
|
||||||
|
)
|
||||||
|
mock_psutil.virtual_memory.return_value = svmem(
|
||||||
|
100 * 1024**2,
|
||||||
|
40 * 1024**2,
|
||||||
|
40.0,
|
||||||
|
60 * 1024**2,
|
||||||
|
30 * 1024**2,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
mock_psutil.net_io_counters.return_value = {
|
||||||
|
"eth0": snetio(100 * 1024**2, 100 * 1024**2, 50, 50, 0, 0, 0, 0),
|
||||||
|
"eth1": snetio(200 * 1024**2, 200 * 1024**2, 150, 150, 0, 0, 0, 0),
|
||||||
|
"vethxyzxyz": snetio(300 * 1024**2, 300 * 1024**2, 150, 150, 0, 0, 0, 0),
|
||||||
|
}
|
||||||
|
mock_psutil.net_if_addrs.return_value = {
|
||||||
|
"eth0": [
|
||||||
|
snicaddr(
|
||||||
|
socket.AF_INET,
|
||||||
|
"192.168.1.1",
|
||||||
|
"255.255.255.0",
|
||||||
|
"255.255.255.255",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
"eth1": [
|
||||||
|
snicaddr(
|
||||||
|
socket.AF_INET,
|
||||||
|
"192.168.10.1",
|
||||||
|
"255.255.255.0",
|
||||||
|
"255.255.255.255",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
"vethxyzxyz": [
|
||||||
|
snicaddr(
|
||||||
|
socket.AF_INET,
|
||||||
|
"172.16.10.1",
|
||||||
|
"255.255.255.0",
|
||||||
|
"255.255.255.255",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
mock_psutil.cpu_percent.return_value = 10.0
|
||||||
|
mock_psutil.boot_time.return_value = 1703973338.0
|
||||||
|
mock_psutil.process_iter.return_value = mock_process
|
||||||
|
# sensors_temperatures not available on MacOS so we
|
||||||
|
# need to override the spec
|
||||||
|
mock_psutil.sensors_temperatures = Mock()
|
||||||
|
mock_psutil.sensors_temperatures.return_value = {
|
||||||
|
"cpu0-thermal": [shwtemp("cpu0-thermal", 50.0, 60.0, 70.0)]
|
||||||
|
}
|
||||||
|
mock_psutil.NoSuchProcess = NoSuchProcess
|
||||||
|
yield mock_psutil
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_util(mock_process) -> Mock:
|
||||||
|
"""Mock psutil."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.systemmonitor.util.psutil", autospec=True
|
||||||
|
) as mock_util:
|
||||||
|
mock_util.net_if_addrs.return_value = {
|
||||||
|
"eth0": [
|
||||||
|
snicaddr(
|
||||||
|
socket.AF_INET,
|
||||||
|
"192.168.1.1",
|
||||||
|
"255.255.255.0",
|
||||||
|
"255.255.255.255",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
"eth1": [
|
||||||
|
snicaddr(
|
||||||
|
socket.AF_INET,
|
||||||
|
"192.168.10.1",
|
||||||
|
"255.255.255.0",
|
||||||
|
"255.255.255.255",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
"vethxyzxyz": [
|
||||||
|
snicaddr(
|
||||||
|
socket.AF_INET,
|
||||||
|
"172.16.10.1",
|
||||||
|
"255.255.255.0",
|
||||||
|
"255.255.255.255",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
mock_process = [MockProcess("python3")]
|
||||||
|
mock_util.process_iter.return_value = mock_process
|
||||||
|
# sensors_temperatures not available on MacOS so we
|
||||||
|
# need to override the spec
|
||||||
|
mock_util.sensors_temperatures = Mock()
|
||||||
|
mock_util.sensors_temperatures.return_value = {
|
||||||
|
"cpu0-thermal": [shwtemp("cpu0-thermal", 50.0, 60.0, 70.0)]
|
||||||
|
}
|
||||||
|
mock_util.disk_partitions.return_value = [
|
||||||
|
sdiskpart("test", "/", "ext4", "", 1, 1),
|
||||||
|
sdiskpart("test2", "/media/share", "ext4", "", 1, 1),
|
||||||
|
sdiskpart("test3", "/incorrect", "", "", 1, 1),
|
||||||
|
sdiskpart("proc", "/proc/run", "proc", "", 1, 1),
|
||||||
|
]
|
||||||
|
mock_util.disk_usage.return_value = sdiskusage(10, 10, 0, 0)
|
||||||
|
yield mock_util
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_os() -> Mock:
|
||||||
|
"""Mock os."""
|
||||||
|
with patch("homeassistant.components.systemmonitor.sensor.os") as mock_os, patch(
|
||||||
|
"homeassistant.components.systemmonitor.util.os"
|
||||||
|
) as mock_os_util:
|
||||||
|
mock_os_util.name = "nt"
|
||||||
|
mock_os.getloadavg.return_value = (1, 2, 3)
|
||||||
|
yield mock_os
|
||||||
|
399
tests/components/systemmonitor/snapshots/test_sensor.ambr
Normal file
399
tests/components/systemmonitor/snapshots/test_sensor.ambr
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
# serializer version: 1
|
||||||
|
# name: test_sensor[System Monitor Disk free / - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_size',
|
||||||
|
'friendly_name': 'System Monitor Disk free /',
|
||||||
|
'icon': 'mdi:harddisk',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfInformation.GIBIBYTES: 'GiB'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Disk free / - state]
|
||||||
|
'0.2'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Disk free /media/share - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_size',
|
||||||
|
'friendly_name': 'System Monitor Disk free /media/share',
|
||||||
|
'icon': 'mdi:harddisk',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfInformation.GIBIBYTES: 'GiB'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Disk free /media/share - state]
|
||||||
|
'0.2'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Disk use (percent) / - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Disk use (percent) /',
|
||||||
|
'icon': 'mdi:harddisk',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Disk use (percent) / - state]
|
||||||
|
'60.0'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Disk use (percent) /home/notexist/ - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Disk use (percent) /home/notexist/',
|
||||||
|
'icon': 'mdi:harddisk',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Disk use (percent) /home/notexist/ - state]
|
||||||
|
'60.0'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Disk use (percent) /media/share - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Disk use (percent) /media/share',
|
||||||
|
'icon': 'mdi:harddisk',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Disk use (percent) /media/share - state]
|
||||||
|
'60.0'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Disk use / - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_size',
|
||||||
|
'friendly_name': 'System Monitor Disk use /',
|
||||||
|
'icon': 'mdi:harddisk',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfInformation.GIBIBYTES: 'GiB'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Disk use / - state]
|
||||||
|
'0.3'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Disk use /media/share - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_size',
|
||||||
|
'friendly_name': 'System Monitor Disk use /media/share',
|
||||||
|
'icon': 'mdi:harddisk',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfInformation.GIBIBYTES: 'GiB'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Disk use /media/share - state]
|
||||||
|
'0.3'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor IPv4 address eth0 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor IPv4 address eth0',
|
||||||
|
'icon': 'mdi:ip-network',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor IPv4 address eth0 - state]
|
||||||
|
'192.168.1.1'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor IPv4 address eth1 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor IPv4 address eth1',
|
||||||
|
'icon': 'mdi:ip-network',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor IPv4 address eth1 - state]
|
||||||
|
'192.168.10.1'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor IPv6 address eth0 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor IPv6 address eth0',
|
||||||
|
'icon': 'mdi:ip-network',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor IPv6 address eth0 - state]
|
||||||
|
'unknown'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor IPv6 address eth1 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor IPv6 address eth1',
|
||||||
|
'icon': 'mdi:ip-network',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor IPv6 address eth1 - state]
|
||||||
|
'unknown'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Last boot - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'timestamp',
|
||||||
|
'friendly_name': 'System Monitor Last boot',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Last boot - state]
|
||||||
|
'2023-12-30T21:55:38+00:00'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Load (15m) - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Load (15m)',
|
||||||
|
'icon': 'mdi:cpu-64-bit',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Load (15m) - state]
|
||||||
|
'3'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Load (1m) - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Load (1m)',
|
||||||
|
'icon': 'mdi:cpu-64-bit',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Load (1m) - state]
|
||||||
|
'1'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Load (5m) - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Load (5m)',
|
||||||
|
'icon': 'mdi:cpu-64-bit',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Load (5m) - state]
|
||||||
|
'2'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Memory free - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_size',
|
||||||
|
'friendly_name': 'System Monitor Memory free',
|
||||||
|
'icon': 'mdi:memory',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Memory free - state]
|
||||||
|
'40.0'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Memory use (percent) - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Memory use (percent)',
|
||||||
|
'icon': 'mdi:memory',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Memory use (percent) - state]
|
||||||
|
'40.0'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Memory use - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_size',
|
||||||
|
'friendly_name': 'System Monitor Memory use',
|
||||||
|
'icon': 'mdi:memory',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Memory use - state]
|
||||||
|
'60.0'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network in eth0 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_size',
|
||||||
|
'friendly_name': 'System Monitor Network in eth0',
|
||||||
|
'icon': 'mdi:server-network',
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network in eth0 - state]
|
||||||
|
'100.0'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network in eth1 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_size',
|
||||||
|
'friendly_name': 'System Monitor Network in eth1',
|
||||||
|
'icon': 'mdi:server-network',
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network in eth1 - state]
|
||||||
|
'200.0'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network out eth0 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_size',
|
||||||
|
'friendly_name': 'System Monitor Network out eth0',
|
||||||
|
'icon': 'mdi:server-network',
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network out eth0 - state]
|
||||||
|
'100.0'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network out eth1 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_size',
|
||||||
|
'friendly_name': 'System Monitor Network out eth1',
|
||||||
|
'icon': 'mdi:server-network',
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network out eth1 - state]
|
||||||
|
'200.0'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network throughput in eth0 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_rate',
|
||||||
|
'friendly_name': 'System Monitor Network throughput in eth0',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfDataRate.MEGABYTES_PER_SECOND: 'MB/s'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network throughput in eth0 - state]
|
||||||
|
'unknown'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network throughput in eth1 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_rate',
|
||||||
|
'friendly_name': 'System Monitor Network throughput in eth1',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfDataRate.MEGABYTES_PER_SECOND: 'MB/s'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network throughput in eth1 - state]
|
||||||
|
'unknown'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network throughput out eth0 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_rate',
|
||||||
|
'friendly_name': 'System Monitor Network throughput out eth0',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfDataRate.MEGABYTES_PER_SECOND: 'MB/s'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network throughput out eth0 - state]
|
||||||
|
'unknown'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network throughput out eth1 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_rate',
|
||||||
|
'friendly_name': 'System Monitor Network throughput out eth1',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfDataRate.MEGABYTES_PER_SECOND: 'MB/s'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Network throughput out eth1 - state]
|
||||||
|
'unknown'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Packets in eth0 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Packets in eth0',
|
||||||
|
'icon': 'mdi:server-network',
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Packets in eth0 - state]
|
||||||
|
'50'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Packets in eth1 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Packets in eth1',
|
||||||
|
'icon': 'mdi:server-network',
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Packets in eth1 - state]
|
||||||
|
'150'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Packets out eth0 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Packets out eth0',
|
||||||
|
'icon': 'mdi:server-network',
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Packets out eth0 - state]
|
||||||
|
'50'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Packets out eth1 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Packets out eth1',
|
||||||
|
'icon': 'mdi:server-network',
|
||||||
|
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Packets out eth1 - state]
|
||||||
|
'150'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Process pip - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Process pip',
|
||||||
|
'icon': 'mdi:cpu-64-bit',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Process pip - state]
|
||||||
|
'on'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Process python3 - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Process python3',
|
||||||
|
'icon': 'mdi:cpu-64-bit',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Process python3 - state]
|
||||||
|
'on'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Processor temperature - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'System Monitor Processor temperature',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Processor temperature - state]
|
||||||
|
'50.0'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Processor use - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Processor use',
|
||||||
|
'icon': 'mdi:cpu-64-bit',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Processor use - state]
|
||||||
|
'10'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Swap free - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_size',
|
||||||
|
'friendly_name': 'System Monitor Swap free',
|
||||||
|
'icon': 'mdi:harddisk',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Swap free - state]
|
||||||
|
'40.0'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Swap use (percent) - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'friendly_name': 'System Monitor Swap use (percent)',
|
||||||
|
'icon': 'mdi:harddisk',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Swap use (percent) - state]
|
||||||
|
'60.0'
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Swap use - attributes]
|
||||||
|
ReadOnlyDict({
|
||||||
|
'device_class': 'data_size',
|
||||||
|
'friendly_name': 'System Monitor Swap use',
|
||||||
|
'icon': 'mdi:harddisk',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[System Monitor Swap use - state]
|
||||||
|
'60.0'
|
||||||
|
# ---
|
60
tests/components/systemmonitor/test_init.py
Normal file
60
tests/components/systemmonitor/test_init.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
"""Test for System Monitor init."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.components.systemmonitor.const import CONF_PROCESS
|
||||||
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||||
|
from homeassistant.const import STATE_OFF
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
|
|
||||||
|
|
||||||
|
async def test_load_unload_entry(
|
||||||
|
hass: HomeAssistant, mock_added_config_entry: ConfigEntry
|
||||||
|
) -> None:
|
||||||
|
"""Test load and unload an entry."""
|
||||||
|
|
||||||
|
assert mock_added_config_entry.state == ConfigEntryState.LOADED
|
||||||
|
assert await hass.config_entries.async_unload(mock_added_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert mock_added_config_entry.state is ConfigEntryState.NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_adding_processor_to_options(
|
||||||
|
hass: HomeAssistant, mock_added_config_entry: ConfigEntry
|
||||||
|
) -> None:
|
||||||
|
"""Test options listener."""
|
||||||
|
process_sensor = hass.states.get("sensor.system_monitor_process_systemd")
|
||||||
|
assert process_sensor is None
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_init(
|
||||||
|
mock_added_config_entry.entry_id
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "init"
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={
|
||||||
|
CONF_PROCESS: ["python3", "pip", "systemd"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["data"] == {
|
||||||
|
"sensor": {
|
||||||
|
CONF_PROCESS: ["python3", "pip", "systemd"],
|
||||||
|
},
|
||||||
|
"resources": [
|
||||||
|
"disk_use_percent_/",
|
||||||
|
"disk_use_percent_/home/notexist/",
|
||||||
|
"memory_free_",
|
||||||
|
"network_out_eth0",
|
||||||
|
"process_python3",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
process_sensor = hass.states.get("sensor.system_monitor_process_systemd")
|
||||||
|
assert process_sensor is not None
|
||||||
|
assert process_sensor.state == STATE_OFF
|
346
tests/components/systemmonitor/test_sensor.py
Normal file
346
tests/components/systemmonitor/test_sensor.py
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
"""Test System Monitor sensor."""
|
||||||
|
from datetime import timedelta
|
||||||
|
import socket
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
from psutil._common import shwtemp, snetio, snicaddr
|
||||||
|
import pytest
|
||||||
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant.components.systemmonitor.sensor import (
|
||||||
|
_read_cpu_temperature,
|
||||||
|
get_cpu_icon,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from .conftest import MockProcess, svmem
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry_enabled_by_default: None,
|
||||||
|
mock_added_config_entry: ConfigEntry,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
) -> None:
|
||||||
|
"""Test the sensor."""
|
||||||
|
memory_sensor = hass.states.get("sensor.system_monitor_memory_free")
|
||||||
|
assert memory_sensor is not None
|
||||||
|
assert memory_sensor.state == "40.0"
|
||||||
|
assert memory_sensor.attributes == {
|
||||||
|
"state_class": "measurement",
|
||||||
|
"unit_of_measurement": "MiB",
|
||||||
|
"device_class": "data_size",
|
||||||
|
"icon": "mdi:memory",
|
||||||
|
"friendly_name": "System Monitor Memory free",
|
||||||
|
}
|
||||||
|
|
||||||
|
process_sensor = hass.states.get("sensor.system_monitor_process_python3")
|
||||||
|
assert process_sensor is not None
|
||||||
|
assert process_sensor.state == STATE_ON
|
||||||
|
|
||||||
|
for entity in er.async_entries_for_config_entry(
|
||||||
|
entity_registry, mock_added_config_entry.entry_id
|
||||||
|
):
|
||||||
|
state = hass.states.get(entity.entity_id)
|
||||||
|
assert state.state == snapshot(name=f"{state.name} - state")
|
||||||
|
assert state.attributes == snapshot(name=f"{state.name} - attributes")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor_not_loading_veth_networks(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry_enabled_by_default: None,
|
||||||
|
mock_added_config_entry: ConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test the sensor."""
|
||||||
|
network_sensor_1 = hass.states.get("sensor.system_monitor_network_out_eth1")
|
||||||
|
network_sensor_2 = hass.states.get(
|
||||||
|
"sensor.sensor.system_monitor_network_out_vethxyzxyz"
|
||||||
|
)
|
||||||
|
assert network_sensor_1 is not None
|
||||||
|
assert network_sensor_1.state == "200.0"
|
||||||
|
assert network_sensor_2 is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor_icon(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry_enabled_by_default: None,
|
||||||
|
mock_util: Mock,
|
||||||
|
mock_psutil: Mock,
|
||||||
|
mock_os: Mock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test the sensor icon for 32bit/64bit system."""
|
||||||
|
|
||||||
|
get_cpu_icon.cache_clear()
|
||||||
|
with patch("sys.maxsize", 2**32):
|
||||||
|
assert get_cpu_icon() == "mdi:cpu-32-bit"
|
||||||
|
get_cpu_icon.cache_clear()
|
||||||
|
with patch("sys.maxsize", 2**64):
|
||||||
|
assert get_cpu_icon() == "mdi:cpu-64-bit"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor_yaml(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry_enabled_by_default: None,
|
||||||
|
mock_psutil: Mock,
|
||||||
|
mock_os: Mock,
|
||||||
|
mock_util: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test the sensor imported from YAML."""
|
||||||
|
config = {
|
||||||
|
"sensor": {
|
||||||
|
"platform": "systemmonitor",
|
||||||
|
"resources": [
|
||||||
|
{"type": "disk_use_percent"},
|
||||||
|
{"type": "disk_use_percent", "arg": "/media/share"},
|
||||||
|
{"type": "memory_free", "arg": "/"},
|
||||||
|
{"type": "network_out", "arg": "eth0"},
|
||||||
|
{"type": "process", "arg": "python3"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert await async_setup_component(hass, "sensor", config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
memory_sensor = hass.states.get("sensor.system_monitor_memory_free")
|
||||||
|
assert memory_sensor is not None
|
||||||
|
assert memory_sensor.state == "40.0"
|
||||||
|
|
||||||
|
process_sensor = hass.states.get("sensor.system_monitor_process_python3")
|
||||||
|
assert process_sensor is not None
|
||||||
|
assert process_sensor.state == STATE_ON
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor_yaml_fails_missing_argument(
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry_enabled_by_default: None,
|
||||||
|
mock_psutil: Mock,
|
||||||
|
mock_os: Mock,
|
||||||
|
mock_util: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test the sensor imported from YAML fails on missing mandatory argument."""
|
||||||
|
config = {
|
||||||
|
"sensor": {
|
||||||
|
"platform": "systemmonitor",
|
||||||
|
"resources": [
|
||||||
|
{"type": "network_in"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert await async_setup_component(hass, "sensor", config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert "Mandatory 'arg' is missing for sensor type 'network_in'" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor_updating(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_added_config_entry: ConfigEntry,
|
||||||
|
mock_psutil: Mock,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test the sensor."""
|
||||||
|
memory_sensor = hass.states.get("sensor.system_monitor_memory_free")
|
||||||
|
assert memory_sensor is not None
|
||||||
|
assert memory_sensor.state == "40.0"
|
||||||
|
|
||||||
|
process_sensor = hass.states.get("sensor.system_monitor_process_python3")
|
||||||
|
assert process_sensor is not None
|
||||||
|
assert process_sensor.state == STATE_ON
|
||||||
|
|
||||||
|
mock_psutil.virtual_memory.side_effect = Exception("Failed to update")
|
||||||
|
freezer.tick(timedelta(minutes=1))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
memory_sensor = hass.states.get("sensor.system_monitor_memory_free")
|
||||||
|
assert memory_sensor is not None
|
||||||
|
assert memory_sensor.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
mock_psutil.virtual_memory.side_effect = None
|
||||||
|
mock_psutil.virtual_memory.return_value = svmem(
|
||||||
|
100 * 1024**2,
|
||||||
|
25 * 1024**2,
|
||||||
|
25.0,
|
||||||
|
60 * 1024**2,
|
||||||
|
30 * 1024**2,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
freezer.tick(timedelta(minutes=1))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
memory_sensor = hass.states.get("sensor.system_monitor_memory_free")
|
||||||
|
assert memory_sensor is not None
|
||||||
|
assert memory_sensor.state == "25.0"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor_process_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_added_config_entry: ConfigEntry,
|
||||||
|
mock_psutil: Mock,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test process not exist failure."""
|
||||||
|
process_sensor = hass.states.get("sensor.system_monitor_process_python3")
|
||||||
|
assert process_sensor is not None
|
||||||
|
assert process_sensor.state == STATE_ON
|
||||||
|
|
||||||
|
_process = MockProcess("python3", True)
|
||||||
|
|
||||||
|
mock_psutil.process_iter.return_value = [_process]
|
||||||
|
|
||||||
|
freezer.tick(timedelta(minutes=1))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
process_sensor = hass.states.get("sensor.system_monitor_process_python3")
|
||||||
|
assert process_sensor is not None
|
||||||
|
# assert process_sensor.state == STATE_ON
|
||||||
|
|
||||||
|
assert "Failed to load process with ID: 1, old name: python3" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor_network_sensors(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry_enabled_by_default: None,
|
||||||
|
mock_added_config_entry: ConfigEntry,
|
||||||
|
mock_psutil: Mock,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test process not exist failure."""
|
||||||
|
network_out_sensor = hass.states.get("sensor.system_monitor_network_out_eth1")
|
||||||
|
packets_out_sensor = hass.states.get("sensor.system_monitor_packets_out_eth1")
|
||||||
|
throughput_network_out_sensor = hass.states.get(
|
||||||
|
"sensor.system_monitor_network_throughput_out_eth1"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert network_out_sensor is not None
|
||||||
|
assert packets_out_sensor is not None
|
||||||
|
assert throughput_network_out_sensor is not None
|
||||||
|
assert network_out_sensor.state == "200.0"
|
||||||
|
assert packets_out_sensor.state == "150"
|
||||||
|
assert throughput_network_out_sensor.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
mock_psutil.net_io_counters.return_value = {
|
||||||
|
"eth0": snetio(200 * 1024**2, 200 * 1024**2, 100, 100, 0, 0, 0, 0),
|
||||||
|
"eth1": snetio(400 * 1024**2, 400 * 1024**2, 300, 300, 0, 0, 0, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
freezer.tick(timedelta(minutes=1))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
network_out_sensor = hass.states.get("sensor.system_monitor_network_out_eth1")
|
||||||
|
packets_out_sensor = hass.states.get("sensor.system_monitor_packets_out_eth1")
|
||||||
|
throughput_network_out_sensor = hass.states.get(
|
||||||
|
"sensor.system_monitor_network_throughput_out_eth1"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert network_out_sensor is not None
|
||||||
|
assert packets_out_sensor is not None
|
||||||
|
assert throughput_network_out_sensor is not None
|
||||||
|
assert network_out_sensor.state == "400.0"
|
||||||
|
assert packets_out_sensor.state == "300"
|
||||||
|
assert float(throughput_network_out_sensor.state) == pytest.approx(3.493, rel=0.1)
|
||||||
|
|
||||||
|
mock_psutil.net_io_counters.return_value = {
|
||||||
|
"eth0": snetio(100 * 1024**2, 100 * 1024**2, 50, 50, 0, 0, 0, 0),
|
||||||
|
}
|
||||||
|
mock_psutil.net_if_addrs.return_value = {
|
||||||
|
"eth0": [
|
||||||
|
snicaddr(
|
||||||
|
socket.AF_INET,
|
||||||
|
"192.168.1.1",
|
||||||
|
"255.255.255.0",
|
||||||
|
"255.255.255.255",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
freezer.tick(timedelta(minutes=1))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
network_out_sensor = hass.states.get("sensor.system_monitor_network_out_eth1")
|
||||||
|
packets_out_sensor = hass.states.get("sensor.system_monitor_packets_out_eth1")
|
||||||
|
throughput_network_out_sensor = hass.states.get(
|
||||||
|
"sensor.system_monitor_network_throughput_out_eth1"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert network_out_sensor is not None
|
||||||
|
assert packets_out_sensor is not None
|
||||||
|
assert throughput_network_out_sensor is not None
|
||||||
|
assert network_out_sensor.state == STATE_UNKNOWN
|
||||||
|
assert packets_out_sensor.state == STATE_UNKNOWN
|
||||||
|
assert throughput_network_out_sensor.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_missing_cpu_temperature(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry_enabled_by_default: None,
|
||||||
|
mock_util: Mock,
|
||||||
|
mock_psutil: Mock,
|
||||||
|
mock_os: Mock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the sensor when temperature missing."""
|
||||||
|
mock_psutil.sensors_temperatures.return_value = {
|
||||||
|
"not_exist": [shwtemp("not_exist", 50.0, 60.0, 70.0)]
|
||||||
|
}
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert "Cannot read CPU / processor temperature information" in caplog.text
|
||||||
|
temp_sensor = hass.states.get("sensor.system_monitor_processor_temperature")
|
||||||
|
assert temp_sensor is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_processor_temperature() -> None:
|
||||||
|
"""Test the disk failures."""
|
||||||
|
|
||||||
|
with patch("sys.platform", "linux"), patch(
|
||||||
|
"homeassistant.components.systemmonitor.sensor.psutil"
|
||||||
|
) as mock_psutil:
|
||||||
|
mock_psutil.sensors_temperatures.return_value = {
|
||||||
|
"cpu0-thermal": [shwtemp("cpu0-thermal", 50.0, 60.0, 70.0)]
|
||||||
|
}
|
||||||
|
temperature = _read_cpu_temperature()
|
||||||
|
assert temperature == 50.0
|
||||||
|
|
||||||
|
with patch("sys.platform", "nt"), patch(
|
||||||
|
"homeassistant.components.systemmonitor.sensor.psutil",
|
||||||
|
) as mock_psutil:
|
||||||
|
mock_psutil.sensors_temperatures.side_effect = AttributeError(
|
||||||
|
"sensors_temperatures not exist"
|
||||||
|
)
|
||||||
|
temperature = _read_cpu_temperature()
|
||||||
|
assert temperature is None
|
||||||
|
|
||||||
|
with patch("sys.platform", "darwin"), patch(
|
||||||
|
"homeassistant.components.systemmonitor.sensor.psutil"
|
||||||
|
) as mock_psutil:
|
||||||
|
mock_psutil.sensors_temperatures.return_value = {
|
||||||
|
"cpu0-thermal": [shwtemp("cpu0-thermal", 50.0, 60.0, 70.0)]
|
||||||
|
}
|
||||||
|
temperature = _read_cpu_temperature()
|
||||||
|
assert temperature == 50.0
|
90
tests/components/systemmonitor/test_util.py
Normal file
90
tests/components/systemmonitor/test_util.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
"""Test System Monitor utils."""
|
||||||
|
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
from psutil._common import sdiskpart
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("side_effect", "error_text"),
|
||||||
|
[
|
||||||
|
(PermissionError("No permission"), "No permission for running user to access"),
|
||||||
|
(OSError("OS error"), "was excluded because of: OS error"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_disk_setup_failure(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry_enabled_by_default: None,
|
||||||
|
mock_psutil: Mock,
|
||||||
|
mock_os: Mock,
|
||||||
|
mock_util: Mock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
side_effect: Exception,
|
||||||
|
error_text: str,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test the disk failures."""
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.systemmonitor.util.psutil.disk_usage",
|
||||||
|
side_effect=side_effect,
|
||||||
|
):
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
disk_sensor = hass.states.get("sensor.system_monitor_disk_free_media_share")
|
||||||
|
assert disk_sensor is None
|
||||||
|
|
||||||
|
assert error_text in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_disk_util(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry_enabled_by_default: None,
|
||||||
|
mock_psutil: Mock,
|
||||||
|
mock_os: Mock,
|
||||||
|
mock_util: Mock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test the disk failures."""
|
||||||
|
|
||||||
|
mock_util.disk_partitions.return_value = [
|
||||||
|
sdiskpart("test", "/", "ext4", "", 1, 1), # Should be ok
|
||||||
|
sdiskpart("test2", "/media/share", "ext4", "", 1, 1), # Should be ok
|
||||||
|
sdiskpart("test3", "/incorrect", "", "", 1, 1), # Should be skipped as no type
|
||||||
|
sdiskpart(
|
||||||
|
"proc", "/proc/run", "proc", "", 1, 1
|
||||||
|
), # Should be skipped as in skipped disk types
|
||||||
|
sdiskpart(
|
||||||
|
"test4",
|
||||||
|
"/tmpfs/", # noqa: S108
|
||||||
|
"tmpfs",
|
||||||
|
"",
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
), # Should be skipped as in skipped disk types
|
||||||
|
sdiskpart("test5", "E:", "cd", "cdrom", 1, 1), # Should be skipped as cdrom
|
||||||
|
]
|
||||||
|
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
disk_sensor1 = hass.states.get("sensor.system_monitor_disk_free")
|
||||||
|
disk_sensor2 = hass.states.get("sensor.system_monitor_disk_free_media_share")
|
||||||
|
disk_sensor3 = hass.states.get("sensor.system_monitor_disk_free_incorrect")
|
||||||
|
disk_sensor4 = hass.states.get("sensor.system_monitor_disk_free_proc_run")
|
||||||
|
disk_sensor5 = hass.states.get("sensor.system_monitor_disk_free_tmpfs")
|
||||||
|
disk_sensor6 = hass.states.get("sensor.system_monitor_disk_free_e")
|
||||||
|
assert disk_sensor1 is not None
|
||||||
|
assert disk_sensor2 is not None
|
||||||
|
assert disk_sensor3 is None
|
||||||
|
assert disk_sensor4 is None
|
||||||
|
assert disk_sensor5 is None
|
||||||
|
assert disk_sensor6 is None
|
Loading…
x
Reference in New Issue
Block a user