Rewrite recorder unittest tests to pytest style test function (#41264)

This commit is contained in:
Ariana Hlavaty 2020-10-06 20:24:13 +01:00 committed by GitHub
parent d35e33610a
commit 1e9e40bf71
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 346 additions and 405 deletions

View file

@ -0,0 +1,24 @@
"""Common test tools."""
import pytest
from homeassistant.components.recorder.const import DATA_INSTANCE
from tests.common import get_test_home_assistant, init_recorder_component
@pytest.fixture
def hass_recorder():
"""Home Assistant fixture with in-memory recorder."""
hass = get_test_home_assistant()
def setup_recorder(config=None):
"""Set up with params."""
init_recorder_component(hass, config)
hass.start()
hass.block_till_done()
hass.data[DATA_INSTANCE].block_till_done()
return hass
yield setup_recorder
hass.stop()

View file

@ -1,9 +1,6 @@
"""The tests for the Recorder component.""" """The tests for the Recorder component."""
# pylint: disable=protected-access # pylint: disable=protected-access
from datetime import datetime, timedelta from datetime import datetime, timedelta
import unittest
import pytest
from homeassistant.components.recorder import ( from homeassistant.components.recorder import (
CONFIG_SCHEMA, CONFIG_SCHEMA,
@ -24,99 +21,69 @@ from homeassistant.util import dt as dt_util
from .common import wait_recording_done from .common import wait_recording_done
from tests.async_mock import patch from tests.async_mock import patch
from tests.common import ( from tests.common import async_fire_time_changed, get_test_home_assistant
async_fire_time_changed,
get_test_home_assistant,
init_recorder_component,
)
class TestRecorder(unittest.TestCase): def test_saving_state(hass, hass_recorder):
"""Test the recorder module.""" """Test saving and restoring a state."""
hass = hass_recorder()
def setUp(self): # pylint: disable=invalid-name entity_id = "test.recorder"
"""Set up things to be run when tests are started.""" state = "restoring_from_db"
self.hass = get_test_home_assistant() attributes = {"test_attr": 5, "test_attr_10": "nice"}
init_recorder_component(self.hass)
self.hass.start()
self.addCleanup(self.tear_down_cleanup)
def tear_down_cleanup(self): hass.states.set(entity_id, state, attributes)
"""Stop everything that was started."""
self.hass.stop()
def test_saving_state(self): wait_recording_done(hass)
"""Test saving and restoring a state."""
entity_id = "test.recorder"
state = "restoring_from_db"
attributes = {"test_attr": 5, "test_attr_10": "nice"}
self.hass.states.set(entity_id, state, attributes) with session_scope(hass=hass) as session:
db_states = list(session.query(States))
assert len(db_states) == 1
assert db_states[0].event_id > 0
state = db_states[0].to_native()
wait_recording_done(self.hass) assert state == _state_empty_context(hass, entity_id)
with session_scope(hass=self.hass) as session:
db_states = list(session.query(States))
assert len(db_states) == 1
assert db_states[0].event_id > 0
state = db_states[0].to_native()
assert state == _state_empty_context(self.hass, entity_id)
def test_saving_event(self):
"""Test saving and restoring an event."""
event_type = "EVENT_TEST"
event_data = {"test_attr": 5, "test_attr_10": "nice"}
events = []
@callback
def event_listener(event):
"""Record events from eventbus."""
if event.event_type == event_type:
events.append(event)
self.hass.bus.listen(MATCH_ALL, event_listener)
self.hass.bus.fire(event_type, event_data)
wait_recording_done(self.hass)
assert len(events) == 1
event = events[0]
self.hass.data[DATA_INSTANCE].block_till_done()
with session_scope(hass=self.hass) as session:
db_events = list(session.query(Events).filter_by(event_type=event_type))
assert len(db_events) == 1
db_event = db_events[0].to_native()
assert event.event_type == db_event.event_type
assert event.data == db_event.data
assert event.origin == db_event.origin
# Recorder uses SQLite and stores datetimes as integer unix timestamps
assert event.time_fired.replace(microsecond=0) == db_event.time_fired.replace(
microsecond=0
)
@pytest.fixture def test_saving_event(hass, hass_recorder):
def hass_recorder(): """Test saving and restoring an event."""
"""Home Assistant fixture with in-memory recorder.""" hass = hass_recorder()
hass = get_test_home_assistant()
def setup_recorder(config=None): event_type = "EVENT_TEST"
"""Set up with params.""" event_data = {"test_attr": 5, "test_attr_10": "nice"}
init_recorder_component(hass, config)
hass.start()
hass.block_till_done()
hass.data[DATA_INSTANCE].block_till_done()
return hass
yield setup_recorder events = []
hass.stop()
@callback
def event_listener(event):
"""Record events from eventbus."""
if event.event_type == event_type:
events.append(event)
hass.bus.listen(MATCH_ALL, event_listener)
hass.bus.fire(event_type, event_data)
wait_recording_done(hass)
assert len(events) == 1
event = events[0]
hass.data[DATA_INSTANCE].block_till_done()
with session_scope(hass=hass) as session:
db_events = list(session.query(Events).filter_by(event_type=event_type))
assert len(db_events) == 1
db_event = db_events[0].to_native()
assert event.event_type == db_event.event_type
assert event.data == db_event.data
assert event.origin == db_event.origin
# Recorder uses SQLite and stores datetimes as integer unix timestamps
assert event.time_fired.replace(microsecond=0) == db_event.time_fired.replace(
microsecond=0
)
def _add_entities(hass, entity_ids): def _add_entities(hass, entity_ids):

View file

@ -1,6 +1,5 @@
"""The tests for the Recorder component.""" """The tests for the Recorder component."""
from datetime import datetime from datetime import datetime
import unittest
import pytest import pytest
import pytz import pytz
@ -21,150 +20,112 @@ from homeassistant.exceptions import InvalidEntityFormatError
from homeassistant.util import dt from homeassistant.util import dt
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
ENGINE = None
SESSION = None def test_from_event_to_db_event():
"""Test converting event to db event."""
event = ha.Event("test_event", {"some_data": 15})
assert event == Events.from_event(event).to_native()
def setUpModule(): # pylint: disable=invalid-name def test_from_event_to_db_state():
"""Set up a database to use.""" """Test converting event to db state."""
global ENGINE state = ha.State("sensor.temperature", "18")
global SESSION event = ha.Event(
EVENT_STATE_CHANGED,
ENGINE = create_engine("sqlite://") {"entity_id": "sensor.temperature", "old_state": None, "new_state": state},
Base.metadata.create_all(ENGINE) context=state.context,
session_factory = sessionmaker(bind=ENGINE) )
SESSION = scoped_session(session_factory) # We don't restore context unless we need it by joining the
# events table on the event_id for state_changed events
state.context = ha.Context(id=None)
assert state == States.from_event(event).to_native()
def tearDownModule(): # pylint: disable=invalid-name def test_from_event_to_delete_state():
"""Close database.""" """Test converting deleting state event to db state."""
global ENGINE event = ha.Event(
global SESSION EVENT_STATE_CHANGED,
{
"entity_id": "sensor.temperature",
"old_state": ha.State("sensor.temperature", "18"),
"new_state": None,
},
)
db_state = States.from_event(event)
ENGINE.dispose() assert db_state.entity_id == "sensor.temperature"
ENGINE = None assert db_state.domain == "sensor"
SESSION = None assert db_state.state == ""
assert db_state.last_changed == event.time_fired
assert db_state.last_updated == event.time_fired
class TestEvents(unittest.TestCase): def test_entity_ids():
"""Test Events model.""" """Test if entity ids helper method works."""
engine = create_engine("sqlite://")
Base.metadata.create_all(engine)
session_factory = sessionmaker(bind=engine)
# pylint: disable=no-self-use session = scoped_session(session_factory)
def test_from_event(self): session.query(Events).delete()
"""Test converting event to db event.""" session.query(States).delete()
event = ha.Event("test_event", {"some_data": 15}) session.query(RecorderRuns).delete()
assert event == Events.from_event(event).to_native()
run = RecorderRuns(
start=datetime(2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC),
end=datetime(2016, 7, 9, 23, 0, 0, tzinfo=dt.UTC),
closed_incorrect=False,
created=datetime(2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC),
)
class TestStates(unittest.TestCase): session.add(run)
"""Test States model.""" session.commit()
# pylint: disable=no-self-use before_run = datetime(2016, 7, 9, 8, 0, 0, tzinfo=dt.UTC)
in_run = datetime(2016, 7, 9, 13, 0, 0, tzinfo=dt.UTC)
in_run2 = datetime(2016, 7, 9, 15, 0, 0, tzinfo=dt.UTC)
in_run3 = datetime(2016, 7, 9, 18, 0, 0, tzinfo=dt.UTC)
after_run = datetime(2016, 7, 9, 23, 30, 0, tzinfo=dt.UTC)
def test_from_event(self): assert run.to_native() == run
"""Test converting event to db state.""" assert run.entity_ids() == []
state = ha.State("sensor.temperature", "18")
event = ha.Event( session.add(
EVENT_STATE_CHANGED, States(
{"entity_id": "sensor.temperature", "old_state": None, "new_state": state}, entity_id="sensor.temperature",
context=state.context, state="20",
last_changed=before_run,
last_updated=before_run,
) )
# We don't restore context unless we need it by joining the )
# events table on the event_id for state_changed events session.add(
state.context = ha.Context(id=None) States(
assert state == States.from_event(event).to_native() entity_id="sensor.sound",
state="10",
def test_from_event_to_delete_state(self): last_changed=after_run,
"""Test converting deleting state event to db state.""" last_updated=after_run,
event = ha.Event(
EVENT_STATE_CHANGED,
{
"entity_id": "sensor.temperature",
"old_state": ha.State("sensor.temperature", "18"),
"new_state": None,
},
) )
db_state = States.from_event(event) )
assert db_state.entity_id == "sensor.temperature" session.add(
assert db_state.domain == "sensor" States(
assert db_state.state == "" entity_id="sensor.humidity",
assert db_state.last_changed == event.time_fired state="76",
assert db_state.last_updated == event.time_fired last_changed=in_run,
last_updated=in_run,
class TestRecorderRuns(unittest.TestCase):
"""Test recorder run model."""
def setUp(self): # pylint: disable=invalid-name
"""Set up recorder runs."""
self.session = session = SESSION()
session.query(Events).delete()
session.query(States).delete()
session.query(RecorderRuns).delete()
self.addCleanup(self.tear_down_cleanup)
def tear_down_cleanup(self):
"""Clean up."""
self.session.rollback()
def test_entity_ids(self):
"""Test if entity ids helper method works."""
run = RecorderRuns(
start=datetime(2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC),
end=datetime(2016, 7, 9, 23, 0, 0, tzinfo=dt.UTC),
closed_incorrect=False,
created=datetime(2016, 7, 9, 11, 0, 0, tzinfo=dt.UTC),
) )
)
self.session.add(run) session.add(
self.session.commit() States(
entity_id="sensor.lux",
before_run = datetime(2016, 7, 9, 8, 0, 0, tzinfo=dt.UTC) state="5",
in_run = datetime(2016, 7, 9, 13, 0, 0, tzinfo=dt.UTC) last_changed=in_run3,
in_run2 = datetime(2016, 7, 9, 15, 0, 0, tzinfo=dt.UTC) last_updated=in_run3,
in_run3 = datetime(2016, 7, 9, 18, 0, 0, tzinfo=dt.UTC)
after_run = datetime(2016, 7, 9, 23, 30, 0, tzinfo=dt.UTC)
assert run.to_native() == run
assert run.entity_ids() == []
self.session.add(
States(
entity_id="sensor.temperature",
state="20",
last_changed=before_run,
last_updated=before_run,
)
)
self.session.add(
States(
entity_id="sensor.sound",
state="10",
last_changed=after_run,
last_updated=after_run,
)
) )
)
self.session.add( assert sorted(run.entity_ids()) == ["sensor.humidity", "sensor.lux"]
States( assert run.entity_ids(in_run2) == ["sensor.humidity"]
entity_id="sensor.humidity",
state="76",
last_changed=in_run,
last_updated=in_run,
)
)
self.session.add(
States(
entity_id="sensor.lux",
state="5",
last_changed=in_run3,
last_updated=in_run3,
)
)
assert sorted(run.entity_ids()) == ["sensor.humidity", "sensor.lux"]
assert run.entity_ids(in_run2) == ["sensor.humidity"]
def test_states_from_native_invalid_entity_id(): def test_states_from_native_invalid_entity_id():

View file

@ -1,7 +1,6 @@
"""Test data purging.""" """Test data purging."""
from datetime import datetime, timedelta from datetime import datetime, timedelta
import json import json
import unittest
from homeassistant.components import recorder from homeassistant.components import recorder
from homeassistant.components.recorder.const import DATA_INSTANCE from homeassistant.components.recorder.const import DATA_INSTANCE
@ -13,226 +12,216 @@ from homeassistant.util import dt as dt_util
from .common import wait_recording_done from .common import wait_recording_done
from tests.async_mock import patch from tests.async_mock import patch
from tests.common import get_test_home_assistant, init_recorder_component
class TestRecorderPurge(unittest.TestCase): def test_purge_old_states(hass, hass_recorder):
"""Base class for common recorder tests.""" """Test deleting old states."""
hass = hass_recorder()
_add_test_states(hass)
def setUp(self): # pylint: disable=invalid-name # make sure we start with 6 states
"""Set up things to be run when tests are started.""" with session_scope(hass=hass) as session:
self.hass = get_test_home_assistant() states = session.query(States)
init_recorder_component(self.hass) assert states.count() == 6
self.hass.start()
self.addCleanup(self.tear_down_cleanup)
def tear_down_cleanup(self): # run purge_old_data()
"""Stop everything that was started.""" finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False)
self.hass.stop() assert not finished
assert states.count() == 4
def _add_test_states(self): finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False)
"""Add multiple states to the db for testing.""" assert not finished
now = datetime.now() assert states.count() == 2
five_days_ago = now - timedelta(days=5)
eleven_days_ago = now - timedelta(days=11)
attributes = {"test_attr": 5, "test_attr_10": "nice"}
self.hass.block_till_done() finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False)
self.hass.data[DATA_INSTANCE].block_till_done() assert finished
wait_recording_done(self.hass) assert states.count() == 2
with recorder.session_scope(hass=self.hass) as session:
for event_id in range(6):
if event_id < 2:
timestamp = eleven_days_ago
state = "autopurgeme"
elif event_id < 4:
timestamp = five_days_ago
state = "purgeme"
else:
timestamp = now
state = "dontpurgeme"
session.add( def test_purge_old_events(hass, hass_recorder):
States( """Test deleting old events."""
entity_id="test.recorder2", hass = hass_recorder()
domain="sensor", _add_test_events(hass)
state=state,
attributes=json.dumps(attributes),
last_changed=timestamp,
last_updated=timestamp,
created=timestamp,
event_id=event_id + 1000,
)
)
def _add_test_events(self): with session_scope(hass=hass) as session:
"""Add a few events for testing.""" events = session.query(Events).filter(Events.event_type.like("EVENT_TEST%"))
now = datetime.now() assert events.count() == 6
five_days_ago = now - timedelta(days=5)
eleven_days_ago = now - timedelta(days=11)
event_data = {"test_attr": 5, "test_attr_10": "nice"}
self.hass.block_till_done() # run purge_old_data()
self.hass.data[DATA_INSTANCE].block_till_done() finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False)
wait_recording_done(self.hass) assert not finished
assert events.count() == 4
with recorder.session_scope(hass=self.hass) as session: finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False)
for event_id in range(6): assert not finished
if event_id < 2: assert events.count() == 2
timestamp = eleven_days_ago
event_type = "EVENT_TEST_AUTOPURGE"
elif event_id < 4:
timestamp = five_days_ago
event_type = "EVENT_TEST_PURGE"
else:
timestamp = now
event_type = "EVENT_TEST"
session.add( # we should only have 2 events left
Events( finished = purge_old_data(hass.data[DATA_INSTANCE], 4, repack=False)
event_type=event_type, assert finished
event_data=json.dumps(event_data), assert events.count() == 2
origin="LOCAL",
created=timestamp,
time_fired=timestamp,
)
)
def _add_test_recorder_runs(self):
"""Add a few recorder_runs for testing."""
now = datetime.now()
five_days_ago = now - timedelta(days=5)
eleven_days_ago = now - timedelta(days=11)
self.hass.block_till_done() def test_purge_method(hass, hass_recorder):
self.hass.data[DATA_INSTANCE].block_till_done() """Test purge method."""
wait_recording_done(self.hass) hass = hass_recorder()
service_data = {"keep_days": 4}
_add_test_events(hass)
_add_test_states(hass)
_add_test_recorder_runs(hass)
with recorder.session_scope(hass=self.hass) as session: # make sure we start with 6 states
for rec_id in range(6): with session_scope(hass=hass) as session:
if rec_id < 2: states = session.query(States)
timestamp = eleven_days_ago assert states.count() == 6
elif rec_id < 4:
timestamp = five_days_ago
else:
timestamp = now
session.add( events = session.query(Events).filter(Events.event_type.like("EVENT_TEST%"))
RecorderRuns( assert events.count() == 6
start=timestamp,
created=dt_util.utcnow(),
end=timestamp + timedelta(days=1),
)
)
def test_purge_old_states(self): recorder_runs = session.query(RecorderRuns)
"""Test deleting old states.""" assert recorder_runs.count() == 7
self._add_test_states()
# make sure we start with 6 states
with session_scope(hass=self.hass) as session:
states = session.query(States)
assert states.count() == 6
# run purge_old_data() hass.data[DATA_INSTANCE].block_till_done()
finished = purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) wait_recording_done(hass)
assert not finished
assert states.count() == 4
finished = purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) # run purge method - no service data, use defaults
assert not finished hass.services.call("recorder", "purge")
assert states.count() == 2 hass.block_till_done()
finished = purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) # Small wait for recorder thread
assert finished hass.data[DATA_INSTANCE].block_till_done()
assert states.count() == 2 wait_recording_done(hass)
def test_purge_old_events(self): # only purged old events
"""Test deleting old events.""" assert states.count() == 4
self._add_test_events() assert events.count() == 4
with session_scope(hass=self.hass) as session: # run purge method - correct service data
events = session.query(Events).filter(Events.event_type.like("EVENT_TEST%")) hass.services.call("recorder", "purge", service_data=service_data)
assert events.count() == 6 hass.block_till_done()
# run purge_old_data() # Small wait for recorder thread
finished = purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) hass.data[DATA_INSTANCE].block_till_done()
assert not finished wait_recording_done(hass)
assert events.count() == 4
finished = purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) # we should only have 2 states left after purging
assert not finished assert states.count() == 2
assert events.count() == 2
# we should only have 2 events left # now we should only have 2 events left
finished = purge_old_data(self.hass.data[DATA_INSTANCE], 4, repack=False) assert events.count() == 2
assert finished
assert events.count() == 2
def test_purge_method(self): # now we should only have 3 recorder runs left
"""Test purge method.""" assert recorder_runs.count() == 3
service_data = {"keep_days": 4}
self._add_test_events()
self._add_test_states()
self._add_test_recorder_runs()
# make sure we start with 6 states assert not ("EVENT_TEST_PURGE" in (event.event_type for event in events.all()))
with session_scope(hass=self.hass) as session:
states = session.query(States)
assert states.count() == 6
events = session.query(Events).filter(Events.event_type.like("EVENT_TEST%")) # run purge method - correct service data, with repack
assert events.count() == 6 with patch("homeassistant.components.recorder.purge._LOGGER") as mock_logger:
service_data["repack"] = True
recorder_runs = session.query(RecorderRuns) hass.services.call("recorder", "purge", service_data=service_data)
assert recorder_runs.count() == 7 hass.block_till_done()
hass.data[DATA_INSTANCE].block_till_done()
self.hass.data[DATA_INSTANCE].block_till_done() wait_recording_done(hass)
wait_recording_done(self.hass) assert (
mock_logger.debug.mock_calls[5][1][0]
# run purge method - no service data, use defaults == "Vacuuming SQL DB to free space"
self.hass.services.call("recorder", "purge")
self.hass.block_till_done()
# Small wait for recorder thread
self.hass.data[DATA_INSTANCE].block_till_done()
wait_recording_done(self.hass)
# only purged old events
assert states.count() == 4
assert events.count() == 4
# run purge method - correct service data
self.hass.services.call("recorder", "purge", service_data=service_data)
self.hass.block_till_done()
# Small wait for recorder thread
self.hass.data[DATA_INSTANCE].block_till_done()
wait_recording_done(self.hass)
# we should only have 2 states left after purging
assert states.count() == 2
# now we should only have 2 events left
assert events.count() == 2
# now we should only have 3 recorder runs left
assert recorder_runs.count() == 3
assert not (
"EVENT_TEST_PURGE" in (event.event_type for event in events.all())
) )
# run purge method - correct service data, with repack
with patch( def _add_test_states(hass):
"homeassistant.components.recorder.purge._LOGGER" """Add multiple states to the db for testing."""
) as mock_logger: now = datetime.now()
service_data["repack"] = True five_days_ago = now - timedelta(days=5)
self.hass.services.call("recorder", "purge", service_data=service_data) eleven_days_ago = now - timedelta(days=11)
self.hass.block_till_done() attributes = {"test_attr": 5, "test_attr_10": "nice"}
self.hass.data[DATA_INSTANCE].block_till_done()
wait_recording_done(self.hass) hass.block_till_done()
assert ( hass.data[DATA_INSTANCE].block_till_done()
mock_logger.debug.mock_calls[5][1][0] wait_recording_done(hass)
== "Vacuuming SQL DB to free space"
with recorder.session_scope(hass=hass) as session:
for event_id in range(6):
if event_id < 2:
timestamp = eleven_days_ago
state = "autopurgeme"
elif event_id < 4:
timestamp = five_days_ago
state = "purgeme"
else:
timestamp = now
state = "dontpurgeme"
session.add(
States(
entity_id="test.recorder2",
domain="sensor",
state=state,
attributes=json.dumps(attributes),
last_changed=timestamp,
last_updated=timestamp,
created=timestamp,
event_id=event_id + 1000,
) )
)
def _add_test_events(hass):
"""Add a few events for testing."""
now = datetime.now()
five_days_ago = now - timedelta(days=5)
eleven_days_ago = now - timedelta(days=11)
event_data = {"test_attr": 5, "test_attr_10": "nice"}
hass.block_till_done()
hass.data[DATA_INSTANCE].block_till_done()
wait_recording_done(hass)
with recorder.session_scope(hass=hass) as session:
for event_id in range(6):
if event_id < 2:
timestamp = eleven_days_ago
event_type = "EVENT_TEST_AUTOPURGE"
elif event_id < 4:
timestamp = five_days_ago
event_type = "EVENT_TEST_PURGE"
else:
timestamp = now
event_type = "EVENT_TEST"
session.add(
Events(
event_type=event_type,
event_data=json.dumps(event_data),
origin="LOCAL",
created=timestamp,
time_fired=timestamp,
)
)
def _add_test_recorder_runs(hass):
"""Add a few recorder_runs for testing."""
now = datetime.now()
five_days_ago = now - timedelta(days=5)
eleven_days_ago = now - timedelta(days=11)
hass.block_till_done()
hass.data[DATA_INSTANCE].block_till_done()
wait_recording_done(hass)
with recorder.session_scope(hass=hass) as session:
for rec_id in range(6):
if rec_id < 2:
timestamp = eleven_days_ago
elif rec_id < 4:
timestamp = five_days_ago
else:
timestamp = now
session.add(
RecorderRuns(
start=timestamp,
created=dt_util.utcnow(),
end=timestamp + timedelta(days=1),
)
)