From 6ec6f0a835b9f24ae24b73e966d0036d7975ee39 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 25 Jun 2022 21:02:50 -0500 Subject: [PATCH] Fix file sensor reading the whole file to get the last line (#73986) --- homeassistant/components/file/manifest.json | 3 +- homeassistant/components/file/sensor.py | 4 +- requirements_all.txt | 3 ++ requirements_test_all.txt | 3 ++ tests/components/file/fixtures/file_empty.txt | 0 tests/components/file/fixtures/file_value.txt | 3 ++ .../file/fixtures/file_value_template.txt | 2 + tests/components/file/test_sensor.py | 50 +++++++++---------- 8 files changed, 40 insertions(+), 28 deletions(-) create mode 100644 tests/components/file/fixtures/file_empty.txt create mode 100644 tests/components/file/fixtures/file_value.txt create mode 100644 tests/components/file/fixtures/file_value_template.txt diff --git a/homeassistant/components/file/manifest.json b/homeassistant/components/file/manifest.json index 8688ed7939c..2283e74a5e7 100644 --- a/homeassistant/components/file/manifest.json +++ b/homeassistant/components/file/manifest.json @@ -3,5 +3,6 @@ "name": "File", "documentation": "https://www.home-assistant.io/integrations/file", "codeowners": ["@fabaff"], - "iot_class": "local_polling" + "iot_class": "local_polling", + "requirements": ["file-read-backwards==2.0.0"] } diff --git a/homeassistant/components/file/sensor.py b/homeassistant/components/file/sensor.py index e69a7701eb9..8c0966f30bd 100644 --- a/homeassistant/components/file/sensor.py +++ b/homeassistant/components/file/sensor.py @@ -4,6 +4,7 @@ from __future__ import annotations import logging import os +from file_read_backwards import FileReadBackwards import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity @@ -77,9 +78,10 @@ class FileSensor(SensorEntity): def update(self): """Get the latest entry from a file and updates the state.""" try: - with open(self._file_path, encoding="utf-8") as file_data: + with FileReadBackwards(self._file_path, encoding="utf-8") as file_data: for line in file_data: data = line + break data = data.strip() except (IndexError, FileNotFoundError, IsADirectoryError, UnboundLocalError): _LOGGER.warning( diff --git a/requirements_all.txt b/requirements_all.txt index dca486c0d3f..7af4684e169 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -637,6 +637,9 @@ feedparser==6.0.2 # homeassistant.components.fibaro fiblary3==0.1.8 +# homeassistant.components.file +file-read-backwards==2.0.0 + # homeassistant.components.fints fints==3.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 73f98c5eafc..04c6f2d2708 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -455,6 +455,9 @@ feedparser==6.0.2 # homeassistant.components.fibaro fiblary3==0.1.8 +# homeassistant.components.file +file-read-backwards==2.0.0 + # homeassistant.components.fivem fivem-api==0.1.2 diff --git a/tests/components/file/fixtures/file_empty.txt b/tests/components/file/fixtures/file_empty.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/components/file/fixtures/file_value.txt b/tests/components/file/fixtures/file_value.txt new file mode 100644 index 00000000000..acfd8fbf1a9 --- /dev/null +++ b/tests/components/file/fixtures/file_value.txt @@ -0,0 +1,3 @@ +43 +45 +21 diff --git a/tests/components/file/fixtures/file_value_template.txt b/tests/components/file/fixtures/file_value_template.txt new file mode 100644 index 00000000000..30a1b0ea8ba --- /dev/null +++ b/tests/components/file/fixtures/file_value_template.txt @@ -0,0 +1,2 @@ +{"temperature": 29, "humidity": 31} +{"temperature": 26, "humidity": 36} diff --git a/tests/components/file/test_sensor.py b/tests/components/file/test_sensor.py index 97fe6250d02..725ccb527f8 100644 --- a/tests/components/file/test_sensor.py +++ b/tests/components/file/test_sensor.py @@ -1,5 +1,5 @@ """The tests for local file sensor platform.""" -from unittest.mock import Mock, mock_open, patch +from unittest.mock import Mock, patch import pytest @@ -7,7 +7,7 @@ from homeassistant.const import STATE_UNKNOWN from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -from tests.common import mock_registry +from tests.common import get_fixture_path, mock_registry @pytest.fixture @@ -21,13 +21,14 @@ def entity_reg(hass): async def test_file_value(hass: HomeAssistant) -> None: """Test the File sensor.""" config = { - "sensor": {"platform": "file", "name": "file1", "file_path": "mock.file1"} + "sensor": { + "platform": "file", + "name": "file1", + "file_path": get_fixture_path("file_value.txt", "file"), + } } - m_open = mock_open(read_data="43\n45\n21") - with patch( - "homeassistant.components.file.sensor.open", m_open, create=True - ), patch.object(hass.config, "is_allowed_path", return_value=True): + with patch.object(hass.config, "is_allowed_path", return_value=True): assert await async_setup_component(hass, "sensor", config) await hass.async_block_till_done() @@ -43,19 +44,12 @@ async def test_file_value_template(hass: HomeAssistant) -> None: "sensor": { "platform": "file", "name": "file2", - "file_path": "mock.file2", + "file_path": get_fixture_path("file_value_template.txt", "file"), "value_template": "{{ value_json.temperature }}", } } - data = ( - '{"temperature": 29, "humidity": 31}\n' + '{"temperature": 26, "humidity": 36}' - ) - - m_open = mock_open(read_data=data) - with patch( - "homeassistant.components.file.sensor.open", m_open, create=True - ), patch.object(hass.config, "is_allowed_path", return_value=True): + with patch.object(hass.config, "is_allowed_path", return_value=True): assert await async_setup_component(hass, "sensor", config) await hass.async_block_till_done() @@ -67,12 +61,15 @@ async def test_file_value_template(hass: HomeAssistant) -> None: @patch("os.access", Mock(return_value=True)) async def test_file_empty(hass: HomeAssistant) -> None: """Test the File sensor with an empty file.""" - config = {"sensor": {"platform": "file", "name": "file3", "file_path": "mock.file"}} + config = { + "sensor": { + "platform": "file", + "name": "file3", + "file_path": get_fixture_path("file_empty.txt", "file"), + } + } - m_open = mock_open(read_data="") - with patch( - "homeassistant.components.file.sensor.open", m_open, create=True - ), patch.object(hass.config, "is_allowed_path", return_value=True): + with patch.object(hass.config, "is_allowed_path", return_value=True): assert await async_setup_component(hass, "sensor", config) await hass.async_block_till_done() @@ -85,13 +82,14 @@ async def test_file_empty(hass: HomeAssistant) -> None: async def test_file_path_invalid(hass: HomeAssistant) -> None: """Test the File sensor with invalid path.""" config = { - "sensor": {"platform": "file", "name": "file4", "file_path": "mock.file4"} + "sensor": { + "platform": "file", + "name": "file4", + "file_path": get_fixture_path("file_value.txt", "file"), + } } - m_open = mock_open(read_data="43\n45\n21") - with patch( - "homeassistant.components.file.sensor.open", m_open, create=True - ), patch.object(hass.config, "is_allowed_path", return_value=False): + with patch.object(hass.config, "is_allowed_path", return_value=False): assert await async_setup_component(hass, "sensor", config) await hass.async_block_till_done()