From 3cba25a89263d06065ede85041f24c4aca2ee232 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Wed, 14 Oct 2020 10:19:12 +0200 Subject: [PATCH] Add test coverage for onewire (#40786) Co-authored-by: Martin Hjelmare <marhje52@gmail.com> --- .coveragerc | 2 - homeassistant/components/onewire/sensor.py | 28 +-- tests/components/onewire/__init__.py | 1 + .../onewire/test_entity_owserver.py | 200 +++++++++++++++++ .../components/onewire/test_entity_sysbus.py | 210 +++++++++--------- tests/components/onewire/test_sensor.py | 43 ++++ 6 files changed, 359 insertions(+), 125 deletions(-) create mode 100644 tests/components/onewire/__init__.py create mode 100644 tests/components/onewire/test_entity_owserver.py create mode 100644 tests/components/onewire/test_sensor.py diff --git a/.coveragerc b/.coveragerc index 8ecba062363..2467ef9fda0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -609,8 +609,6 @@ omit = homeassistant/components/omnilogic/__init__.py homeassistant/components/omnilogic/common.py homeassistant/components/omnilogic/sensor.py - homeassistant/components/onewire/const.py - homeassistant/components/onewire/sensor.py homeassistant/components/onkyo/media_player.py homeassistant/components/onvif/__init__.py homeassistant/components/onvif/base.py diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index 4c6564b2fec..d6e8ef50f46 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -3,12 +3,7 @@ from glob import glob import logging import os -from pi1wire import ( - InvalidCRCException, - NotFoundSensorException, - Pi1Wire, - UnsupportResponseException, -) +from pi1wire import InvalidCRCException, Pi1Wire, UnsupportResponseException from pyownet import protocol import voluptuous as vol @@ -145,7 +140,9 @@ def get_entities(config): conf_type = CONF_TYPE_OWSERVER elif base_dir == DEFAULT_SYSBUS_MOUNT_DIR: conf_type = CONF_TYPE_SYSBUS - else: + else: # pragma: no cover + # This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated. + # https://developers.home-assistant.io/docs/creating_platform_code_review/#5-communication-with-devicesservices conf_type = CONF_TYPE_OWFS entities = [] @@ -230,7 +227,9 @@ def get_entities(config): ) # We have an owfs mounted - else: + else: # pragma: no cover + # This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated. + # https://developers.home-assistant.io/docs/creating_platform_code_review/#5-communication-with-devicesservices _LOGGER.debug("Initializing using OWFS %s", base_dir) for family_file_path in glob(os.path.join(base_dir, "*", "family")): with open(family_file_path) as family_file: @@ -303,9 +302,7 @@ class OneWireProxy(OneWire): def _read_value_ownet(self): """Read a value from the owserver.""" - if self._owproxy: - return self._owproxy.read(self._device_file).decode().lstrip() - return None + return self._owproxy.read(self._device_file).decode().lstrip() def update(self): """Get the latest data from the device.""" @@ -339,15 +336,18 @@ class OneWireDirect(OneWire): except ( FileNotFoundError, InvalidCRCException, - NotFoundSensorException, UnsupportResponseException, ) as ex: _LOGGER.warning("Cannot read from sensor %s: %s", self._device_file, ex) self._state = value -class OneWireOWFS(OneWire): - """Implementation of a 1-Wire sensor through owfs.""" +class OneWireOWFS(OneWire): # pragma: no cover + """Implementation of a 1-Wire sensor through owfs. + + This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated. + https://developers.home-assistant.io/docs/creating_platform_code_review/#5-communication-with-devicesservices + """ def _read_value_raw(self): """Read the value as it is returned by the sensor.""" diff --git a/tests/components/onewire/__init__.py b/tests/components/onewire/__init__.py new file mode 100644 index 00000000000..e5de65bc71e --- /dev/null +++ b/tests/components/onewire/__init__.py @@ -0,0 +1 @@ +"""Tests for 1-Wire integration.""" diff --git a/tests/components/onewire/test_entity_owserver.py b/tests/components/onewire/test_entity_owserver.py new file mode 100644 index 00000000000..290c744ab94 --- /dev/null +++ b/tests/components/onewire/test_entity_owserver.py @@ -0,0 +1,200 @@ +"""Tests for 1-Wire devices connected on OWServer.""" +from unittest.mock import patch + +from pyownet.protocol import Error as ProtocolError +import pytest + +from homeassistant.components.onewire.const import DEFAULT_OWSERVER_PORT, DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.const import PERCENTAGE, TEMP_CELSIUS +from homeassistant.setup import async_setup_component + +from tests.common import mock_registry + +MOCK_CONFIG = { + "sensor": { + "platform": DOMAIN, + "host": "localhost", + "port": DEFAULT_OWSERVER_PORT, + "names": { + "10.111111111111": "My DS18B20", + }, + } +} + +MOCK_DEVICE_SENSORS = { + "00.111111111111": {"sensors": []}, + "10.111111111111": { + "sensors": [ + { + "entity_id": "sensor.my_ds18b20_temperature", + "unique_id": "/10.111111111111/temperature", + "injected_value": b" 25.123", + "result": "25.1", + "unit": TEMP_CELSIUS, + }, + ] + }, + "1D.111111111111": { + "sensors": [ + { + "entity_id": "sensor.1d_111111111111_counter_a", + "unique_id": "/1D.111111111111/counter.A", + "injected_value": b" 251123", + "result": "251123", + "unit": "count", + }, + { + "entity_id": "sensor.1d_111111111111_counter_b", + "unique_id": "/1D.111111111111/counter.B", + "injected_value": b" 248125", + "result": "248125", + "unit": "count", + }, + ] + }, + "22.111111111111": { + "sensors": [ + { + "entity_id": "sensor.22_111111111111_temperature", + "unique_id": "/22.111111111111/temperature", + "injected_value": ProtocolError, + "result": "unknown", + "unit": TEMP_CELSIUS, + }, + ] + }, + "28.111111111111": { + "sensors": [ + { + "entity_id": "sensor.28_111111111111_temperature", + "unique_id": "/28.111111111111/temperature", + "injected_value": b" 26.984", + "result": "27.0", + "unit": TEMP_CELSIUS, + }, + ] + }, + "3B.111111111111": { + "sensors": [ + { + "entity_id": "sensor.3b_111111111111_temperature", + "unique_id": "/3B.111111111111/temperature", + "injected_value": b" 28.243", + "result": "28.2", + "unit": TEMP_CELSIUS, + }, + ] + }, + "42.111111111111": { + "sensors": [ + { + "entity_id": "sensor.42_111111111111_temperature", + "unique_id": "/42.111111111111/temperature", + "injected_value": b" 29.123", + "result": "29.1", + "unit": TEMP_CELSIUS, + }, + ] + }, + "EF.111111111111": { + "inject_reads": [ + b"HobbyBoards_EF", # read type + ], + "sensors": [ + { + "entity_id": "sensor.ef_111111111111_humidity", + "unique_id": "/EF.111111111111/humidity/humidity_corrected", + "injected_value": b" 67.745", + "result": "67.7", + "unit": PERCENTAGE, + }, + { + "entity_id": "sensor.ef_111111111111_humidity_raw", + "unique_id": "/EF.111111111111/humidity/humidity_raw", + "injected_value": b" 65.541", + "result": "65.5", + "unit": PERCENTAGE, + }, + { + "entity_id": "sensor.ef_111111111111_temperature", + "unique_id": "/EF.111111111111/humidity/temperature", + "injected_value": b" 25.123", + "result": "25.1", + "unit": TEMP_CELSIUS, + }, + ], + }, + "EF.111111111112": { + "inject_reads": [ + b"HB_MOISTURE_METER", # read type + b" 1", # read is_leaf_0 + b" 1", # read is_leaf_1 + b" 0", # read is_leaf_2 + b" 0", # read is_leaf_3 + ], + "sensors": [ + { + "entity_id": "sensor.ef_111111111112_wetness_0", + "unique_id": "/EF.111111111112/moisture/sensor.0", + "injected_value": b" 41.745", + "result": "41.7", + "unit": PERCENTAGE, + }, + { + "entity_id": "sensor.ef_111111111112_wetness_1", + "unique_id": "/EF.111111111112/moisture/sensor.1", + "injected_value": b" 42.541", + "result": "42.5", + "unit": PERCENTAGE, + }, + { + "entity_id": "sensor.ef_111111111112_moisture_2", + "unique_id": "/EF.111111111112/moisture/sensor.2", + "injected_value": b" 43.123", + "result": "43.1", + "unit": "cb", + }, + { + "entity_id": "sensor.ef_111111111112_moisture_3", + "unique_id": "/EF.111111111112/moisture/sensor.3", + "injected_value": b" 44.123", + "result": "44.1", + "unit": "cb", + }, + ], + }, +} + + +@pytest.mark.parametrize("device_id", MOCK_DEVICE_SENSORS.keys()) +async def test_owserver_setup_valid_device(hass, device_id): + """Test for 1-Wire device.""" + entity_registry = mock_registry(hass) + + dir_return_value = [f"/{device_id}/"] + read_side_effect = [device_id[0:2].encode()] + if "inject_reads" in MOCK_DEVICE_SENSORS[device_id]: + read_side_effect += MOCK_DEVICE_SENSORS[device_id]["inject_reads"] + + expected_sensors = MOCK_DEVICE_SENSORS[device_id]["sensors"] + for expected_sensor in expected_sensors: + read_side_effect.append(expected_sensor["injected_value"]) + + with patch("homeassistant.components.onewire.sensor.protocol.proxy") as owproxy: + owproxy.return_value.dir.return_value = dir_return_value + owproxy.return_value.read.side_effect = read_side_effect + + assert await async_setup_component(hass, SENSOR_DOMAIN, MOCK_CONFIG) + await hass.async_block_till_done() + + assert len(entity_registry.entities) == len(expected_sensors) + + for expected_sensor in expected_sensors: + entity_id = expected_sensor["entity_id"] + registry_entry = entity_registry.entities.get(entity_id) + assert registry_entry is not None + assert registry_entry.unique_id == expected_sensor["unique_id"] + assert registry_entry.unit_of_measurement == expected_sensor["unit"] + state = hass.states.get(entity_id) + assert state.state == expected_sensor["result"] diff --git a/tests/components/onewire/test_entity_sysbus.py b/tests/components/onewire/test_entity_sysbus.py index 4667ab641e7..8a233315ab2 100644 --- a/tests/components/onewire/test_entity_sysbus.py +++ b/tests/components/onewire/test_entity_sysbus.py @@ -1,129 +1,121 @@ -"""Tests for 1-Wire temperature sensor (device family 10, 22, 28, 3B, 42) connected on SysBus.""" -from datetime import datetime, timedelta +"""Tests for 1-Wire devices connected on SysBus.""" from unittest.mock import PropertyMock, patch -from pi1wire import ( - InvalidCRCException, - NotFoundSensorException, - UnsupportResponseException, -) +from pi1wire import InvalidCRCException, UnsupportResponseException +import pytest -from homeassistant.components.onewire.const import ( - DEFAULT_OWSERVER_PORT, - DEFAULT_SYSBUS_MOUNT_DIR, - DOMAIN, -) +from homeassistant.components.onewire.const import DEFAULT_SYSBUS_MOUNT_DIR, DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import TEMP_CELSIUS from homeassistant.setup import async_setup_component -from tests.common import async_fire_time_changed, mock_registry +from tests.common import mock_registry -MOCK_DEVICE_ID = "28-111111111111" -MOCK_DEVICE_NAME = "My DS18B20" -MOCK_ENTITY_ID = "sensor.my_ds18b20_temperature" - - -async def test_onewiredirect_setup_valid_device(hass): +MOCK_CONFIG = { + "sensor": { + "platform": DOMAIN, + "mount_dir": DEFAULT_SYSBUS_MOUNT_DIR, + "names": { + "10-111111111111": "My DS18B20", + }, + } +} + +MOCK_DEVICE_SENSORS = { + "00-111111111111": {"sensors": []}, + "10-111111111111": { + "sensors": [ + { + "entity_id": "sensor.my_ds18b20_temperature", + "unique_id": "/sys/bus/w1/devices/10-111111111111/w1_slave", + "injected_value": 25.123, + "result": "25.1", + "unit": TEMP_CELSIUS, + }, + ] + }, + "1D-111111111111": {"sensors": []}, + "22-111111111111": { + "sensors": [ + { + "entity_id": "sensor.22_111111111111_temperature", + "unique_id": "/sys/bus/w1/devices/22-111111111111/w1_slave", + "injected_value": FileNotFoundError, + "result": "unknown", + "unit": TEMP_CELSIUS, + }, + ] + }, + "28-111111111111": { + "sensors": [ + { + "entity_id": "sensor.28_111111111111_temperature", + "unique_id": "/sys/bus/w1/devices/28-111111111111/w1_slave", + "injected_value": InvalidCRCException, + "result": "unknown", + "unit": TEMP_CELSIUS, + }, + ] + }, + "3B-111111111111": { + "sensors": [ + { + "entity_id": "sensor.3b_111111111111_temperature", + "unique_id": "/sys/bus/w1/devices/3B-111111111111/w1_slave", + "injected_value": 29.993, + "result": "30.0", + "unit": TEMP_CELSIUS, + }, + ] + }, + "42-111111111111": { + "sensors": [ + { + "entity_id": "sensor.42_111111111111_temperature", + "unique_id": "/sys/bus/w1/devices/42-111111111111/w1_slave", + "injected_value": UnsupportResponseException, + "result": "unknown", + "unit": TEMP_CELSIUS, + }, + ] + }, + "EF-111111111111": { + "sensors": [], + }, + "EF-111111111112": { + "sensors": [], + }, +} + + +@pytest.mark.parametrize("device_id", MOCK_DEVICE_SENSORS.keys()) +async def test_onewiredirect_setup_valid_device(hass, device_id): """Test that sysbus config entry works correctly.""" entity_registry = mock_registry(hass) - config = { - "sensor": { - "platform": DOMAIN, - "mount_dir": DEFAULT_SYSBUS_MOUNT_DIR, - "port": DEFAULT_OWSERVER_PORT, - "names": { - MOCK_DEVICE_ID: MOCK_DEVICE_NAME, - }, - } - } + + read_side_effect = [] + expected_sensors = MOCK_DEVICE_SENSORS[device_id]["sensors"] + for expected_sensor in expected_sensors: + read_side_effect.append(expected_sensor["injected_value"]) with patch( "homeassistant.components.onewire.sensor.Pi1Wire" ) as mock_pi1wire, patch("pi1wire.OneWire") as mock_owsensor: type(mock_owsensor).mac_address = PropertyMock( - return_value=MOCK_DEVICE_ID.replace("-", "") + return_value=device_id.replace("-", "") ) - mock_owsensor.get_temperature.side_effect = [ - 25.123, - FileNotFoundError, - 25.223, - InvalidCRCException, - 25.323, - NotFoundSensorException, - 25.423, - UnsupportResponseException, - 25.523, - ] + mock_owsensor.get_temperature.side_effect = read_side_effect mock_pi1wire.return_value.find_all_sensors.return_value = [mock_owsensor] - assert await async_setup_component(hass, SENSOR_DOMAIN, config) - await hass.async_block_till_done() - - assert len(entity_registry.entities) == 1 - registry_entry = entity_registry.entities.get(MOCK_ENTITY_ID) - assert registry_entry is not None - assert ( - registry_entry.unique_id == f"/sys/bus/w1/devices/{MOCK_DEVICE_ID}/w1_slave" - ) - assert registry_entry.unit_of_measurement == TEMP_CELSIUS - - # 25.123 - current_time = datetime.now() - state = hass.states.get(MOCK_ENTITY_ID) - assert state.state == "25.1" - - # FileNotFoundError - current_time = current_time + timedelta(minutes=2) - async_fire_time_changed(hass, current_time) - await hass.async_block_till_done() - state = hass.states.get(MOCK_ENTITY_ID) - assert state.state == "unknown" - - # 25.223 - current_time = current_time + timedelta(minutes=2) - async_fire_time_changed(hass, current_time) - await hass.async_block_till_done() - state = hass.states.get(MOCK_ENTITY_ID) - assert state.state == "25.2" - - # InvalidCRCException - current_time = current_time + timedelta(minutes=2) - async_fire_time_changed(hass, current_time) + assert await async_setup_component(hass, SENSOR_DOMAIN, MOCK_CONFIG) await hass.async_block_till_done() - state = hass.states.get(MOCK_ENTITY_ID) - assert state.state == "unknown" - # 25.323 - current_time = current_time + timedelta(minutes=2) - async_fire_time_changed(hass, current_time) - await hass.async_block_till_done() - state = hass.states.get(MOCK_ENTITY_ID) - assert state.state == "25.3" - - # NotFoundSensorException - current_time = current_time + timedelta(minutes=2) - async_fire_time_changed(hass, current_time) - await hass.async_block_till_done() - state = hass.states.get(MOCK_ENTITY_ID) - assert state.state == "unknown" - - # 25.423 - current_time = current_time + timedelta(minutes=2) - async_fire_time_changed(hass, current_time) - await hass.async_block_till_done() - state = hass.states.get(MOCK_ENTITY_ID) - assert state.state == "25.4" + assert len(entity_registry.entities) == len(expected_sensors) - # UnsupportResponseException - current_time = current_time + timedelta(minutes=2) - async_fire_time_changed(hass, current_time) - await hass.async_block_till_done() - state = hass.states.get(MOCK_ENTITY_ID) - assert state.state == "unknown" - - # 25.523 - current_time = current_time + timedelta(minutes=2) - async_fire_time_changed(hass, current_time) - await hass.async_block_till_done() - state = hass.states.get(MOCK_ENTITY_ID) - assert state.state == "25.5" + for expected_sensor in expected_sensors: + entity_id = expected_sensor["entity_id"] + registry_entry = entity_registry.entities.get(entity_id) + assert registry_entry is not None + assert registry_entry.unique_id == expected_sensor["unique_id"] + assert registry_entry.unit_of_measurement == expected_sensor["unit"] + state = hass.states.get(entity_id) + assert state.state == expected_sensor["result"] diff --git a/tests/components/onewire/test_sensor.py b/tests/components/onewire/test_sensor.py new file mode 100644 index 00000000000..0aa02a9906d --- /dev/null +++ b/tests/components/onewire/test_sensor.py @@ -0,0 +1,43 @@ +"""Tests for 1-Wire sensor platform.""" +from homeassistant.components.onewire.const import DEFAULT_SYSBUS_MOUNT_DIR +import homeassistant.components.sensor as sensor +from homeassistant.setup import async_setup_component + +from tests.common import assert_setup_component + + +async def test_setup_minimum(hass): + """Test setup with minimum configuration.""" + config = {"sensor": {"platform": "onewire"}} + with assert_setup_component(1, "sensor"): + assert await async_setup_component(hass, sensor.DOMAIN, config) + await hass.async_block_till_done() + + +async def test_setup_sysbus(hass): + """Test setup with SysBus configuration.""" + config = { + "sensor": { + "platform": "onewire", + "mount_dir": DEFAULT_SYSBUS_MOUNT_DIR, + } + } + with assert_setup_component(1, "sensor"): + assert await async_setup_component(hass, sensor.DOMAIN, config) + await hass.async_block_till_done() + + +async def test_setup_owserver(hass): + """Test setup with OWServer configuration.""" + config = {"sensor": {"platform": "onewire", "host": "localhost"}} + with assert_setup_component(1, "sensor"): + assert await async_setup_component(hass, sensor.DOMAIN, config) + await hass.async_block_till_done() + + +async def test_setup_owserver_with_port(hass): + """Test setup with OWServer configuration.""" + config = {"sensor": {"platform": "onewire", "host": "localhost", "port": "1234"}} + with assert_setup_component(1, "sensor"): + assert await async_setup_component(hass, sensor.DOMAIN, config) + await hass.async_block_till_done() -- GitLab