Do not let one bad statistic spoil the bunch (#55942)

This commit is contained in:
Erik Montnemery 2021-09-08 16:55:40 +02:00 committed by GitHub
parent c514f72c70
commit 22e6ddf8df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 113 additions and 2 deletions

View File

@ -8,6 +8,7 @@ import logging
from typing import TYPE_CHECKING, Any, Callable from typing import TYPE_CHECKING, Any, Callable
from sqlalchemy import bindparam from sqlalchemy import bindparam
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext import baked from sqlalchemy.ext import baked
from sqlalchemy.orm.scoping import scoped_session from sqlalchemy.orm.scoping import scoped_session
@ -215,7 +216,14 @@ def compile_statistics(instance: Recorder, start: datetime) -> bool:
metadata_id = _update_or_add_metadata( metadata_id = _update_or_add_metadata(
instance.hass, session, entity_id, stat["meta"] instance.hass, session, entity_id, stat["meta"]
) )
try:
session.add(Statistics.from_stats(metadata_id, start, stat["stat"])) session.add(Statistics.from_stats(metadata_id, start, stat["stat"]))
except SQLAlchemyError:
_LOGGER.exception(
"Unexpected exception when inserting statistics %s:%s ",
metadata_id,
stat,
)
session.add(StatisticsRuns(start=start)) session.add(StatisticsRuns(start=start))
return True return True

View File

@ -3,11 +3,15 @@
from datetime import timedelta from datetime import timedelta
from unittest.mock import patch, sentinel from unittest.mock import patch, sentinel
import pytest
from pytest import approx from pytest import approx
from homeassistant.components.recorder import history from homeassistant.components.recorder import history
from homeassistant.components.recorder.const import DATA_INSTANCE from homeassistant.components.recorder.const import DATA_INSTANCE
from homeassistant.components.recorder.models import process_timestamp_to_utc_isoformat from homeassistant.components.recorder.models import (
Statistics,
process_timestamp_to_utc_isoformat,
)
from homeassistant.components.recorder.statistics import ( from homeassistant.components.recorder.statistics import (
get_last_statistics, get_last_statistics,
statistics_during_period, statistics_during_period,
@ -94,6 +98,105 @@ def test_compile_hourly_statistics(hass_recorder):
assert stats == {} assert stats == {}
@pytest.fixture
def mock_sensor_statistics():
"""Generate some fake statistics."""
sensor_stats = {
"meta": {"unit_of_measurement": "dogs", "has_mean": True, "has_sum": False},
"stat": {},
}
def get_fake_stats():
return {
"sensor.test1": sensor_stats,
"sensor.test2": sensor_stats,
"sensor.test3": sensor_stats,
}
with patch(
"homeassistant.components.sensor.recorder.compile_statistics",
return_value=get_fake_stats(),
):
yield
@pytest.fixture
def mock_from_stats():
"""Mock out Statistics.from_stats."""
counter = 0
real_from_stats = Statistics.from_stats
def from_stats(metadata_id, start, stats):
nonlocal counter
if counter == 0 and metadata_id == 2:
counter += 1
return None
return real_from_stats(metadata_id, start, stats)
with patch(
"homeassistant.components.recorder.statistics.Statistics.from_stats",
side_effect=from_stats,
autospec=True,
):
yield
def test_compile_hourly_statistics_exception(
hass_recorder, mock_sensor_statistics, mock_from_stats
):
"""Test exception handling when compiling hourly statistics."""
def mock_from_stats():
raise ValueError
hass = hass_recorder()
recorder = hass.data[DATA_INSTANCE]
setup_component(hass, "sensor", {})
now = dt_util.utcnow()
recorder.do_adhoc_statistics(period="hourly", start=now)
recorder.do_adhoc_statistics(period="hourly", start=now + timedelta(hours=1))
wait_recording_done(hass)
expected_1 = {
"statistic_id": "sensor.test1",
"start": process_timestamp_to_utc_isoformat(now),
"mean": None,
"min": None,
"max": None,
"last_reset": None,
"state": None,
"sum": None,
}
expected_2 = {
"statistic_id": "sensor.test1",
"start": process_timestamp_to_utc_isoformat(now + timedelta(hours=1)),
"mean": None,
"min": None,
"max": None,
"last_reset": None,
"state": None,
"sum": None,
}
expected_stats1 = [
{**expected_1, "statistic_id": "sensor.test1"},
{**expected_2, "statistic_id": "sensor.test1"},
]
expected_stats2 = [
{**expected_2, "statistic_id": "sensor.test2"},
]
expected_stats3 = [
{**expected_1, "statistic_id": "sensor.test3"},
{**expected_2, "statistic_id": "sensor.test3"},
]
stats = statistics_during_period(hass, now)
assert stats == {
"sensor.test1": expected_stats1,
"sensor.test2": expected_stats2,
"sensor.test3": expected_stats3,
}
def test_rename_entity(hass_recorder): def test_rename_entity(hass_recorder):
"""Test statistics is migrated when entity_id is changed.""" """Test statistics is migrated when entity_id is changed."""
hass = hass_recorder() hass = hass_recorder()