Fix flaky history test

This commit is contained in:
Paulus Schoutsen 2015-12-27 09:39:22 -08:00
parent 0d64f4a2d5
commit 9e1ecd7124
2 changed files with 61 additions and 54 deletions

View file

@ -16,7 +16,7 @@ import json
import atexit import atexit
from homeassistant.core import Event, EventOrigin, State from homeassistant.core import Event, EventOrigin, State
import homeassistant.util.dt as date_util import homeassistant.util.dt as dt_util
from homeassistant.remote import JSONEncoder from homeassistant.remote import JSONEncoder
from homeassistant.const import ( from homeassistant.const import (
MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED, MATCH_ALL, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
@ -62,8 +62,8 @@ def row_to_state(row):
try: try:
return State( return State(
row[1], row[2], json.loads(row[3]), row[1], row[2], json.loads(row[3]),
date_util.utc_from_timestamp(row[4]), dt_util.utc_from_timestamp(row[4]),
date_util.utc_from_timestamp(row[5])) dt_util.utc_from_timestamp(row[5]))
except ValueError: except ValueError:
# When json.loads fails # When json.loads fails
_LOGGER.exception("Error converting row to state: %s", row) _LOGGER.exception("Error converting row to state: %s", row)
@ -74,7 +74,7 @@ def row_to_event(row):
""" Convert a databse row to an event. """ """ Convert a databse row to an event. """
try: try:
return Event(row[1], json.loads(row[2]), EventOrigin(row[3]), return Event(row[1], json.loads(row[2]), EventOrigin(row[3]),
date_util.utc_from_timestamp(row[5])) dt_util.utc_from_timestamp(row[5]))
except ValueError: except ValueError:
# When json.loads fails # When json.loads fails
_LOGGER.exception("Error converting row to event: %s", row) _LOGGER.exception("Error converting row to event: %s", row)
@ -116,10 +116,10 @@ class RecorderRun(object):
self.start = _INSTANCE.recording_start self.start = _INSTANCE.recording_start
self.closed_incorrect = False self.closed_incorrect = False
else: else:
self.start = date_util.utc_from_timestamp(row[1]) self.start = dt_util.utc_from_timestamp(row[1])
if row[2] is not None: if row[2] is not None:
self.end = date_util.utc_from_timestamp(row[2]) self.end = dt_util.utc_from_timestamp(row[2])
self.closed_incorrect = bool(row[3]) self.closed_incorrect = bool(row[3])
@ -169,8 +169,8 @@ class Recorder(threading.Thread):
self.queue = queue.Queue() self.queue = queue.Queue()
self.quit_object = object() self.quit_object = object()
self.lock = threading.Lock() self.lock = threading.Lock()
self.recording_start = date_util.utcnow() self.recording_start = dt_util.utcnow()
self.utc_offset = date_util.now().utcoffset().total_seconds() self.utc_offset = dt_util.now().utcoffset().total_seconds()
def start_recording(event): def start_recording(event):
""" Start recording. """ """ Start recording. """
@ -217,10 +217,11 @@ class Recorder(threading.Thread):
def shutdown(self, event): def shutdown(self, event):
""" Tells the recorder to shut down. """ """ Tells the recorder to shut down. """
self.queue.put(self.quit_object) self.queue.put(self.quit_object)
self.block_till_done()
def record_state(self, entity_id, state, event_id): def record_state(self, entity_id, state, event_id):
""" Save a state to the database. """ """ Save a state to the database. """
now = date_util.utcnow() now = dt_util.utcnow()
# State got deleted # State got deleted
if state is None: if state is None:
@ -247,7 +248,7 @@ class Recorder(threading.Thread):
""" Save an event to the database. """ """ Save an event to the database. """
info = ( info = (
event.event_type, json.dumps(event.data, cls=JSONEncoder), event.event_type, json.dumps(event.data, cls=JSONEncoder),
str(event.origin), date_util.utcnow(), event.time_fired, str(event.origin), dt_util.utcnow(), event.time_fired,
self.utc_offset self.utc_offset
) )
@ -307,7 +308,7 @@ class Recorder(threading.Thread):
def save_migration(migration_id): def save_migration(migration_id):
""" Save and commit a migration to the database. """ """ Save and commit a migration to the database. """
cur.execute('INSERT INTO schema_version VALUES (?, ?)', cur.execute('INSERT INTO schema_version VALUES (?, ?)',
(migration_id, date_util.utcnow())) (migration_id, dt_util.utcnow()))
self.conn.commit() self.conn.commit()
_LOGGER.info("Database migrated to version %d", migration_id) _LOGGER.info("Database migrated to version %d", migration_id)
@ -420,18 +421,18 @@ class Recorder(threading.Thread):
self.query( self.query(
"""INSERT INTO recorder_runs (start, created, utc_offset) """INSERT INTO recorder_runs (start, created, utc_offset)
VALUES (?, ?, ?)""", VALUES (?, ?, ?)""",
(self.recording_start, date_util.utcnow(), self.utc_offset)) (self.recording_start, dt_util.utcnow(), self.utc_offset))
def _close_run(self): def _close_run(self):
""" Save end time for current run. """ """ Save end time for current run. """
self.query( self.query(
"UPDATE recorder_runs SET end=? WHERE start=?", "UPDATE recorder_runs SET end=? WHERE start=?",
(date_util.utcnow(), self.recording_start)) (dt_util.utcnow(), self.recording_start))
def _adapt_datetime(datetimestamp): def _adapt_datetime(datetimestamp):
""" Turn a datetime into an integer for in the DB. """ """ Turn a datetime into an integer for in the DB. """
return date_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp() return dt_util.as_utc(datetimestamp.replace(microsecond=0)).timestamp()
def _verify_instance(): def _verify_instance():

View file

@ -5,11 +5,10 @@ tests.test_component_history
Tests the history component. Tests the history component.
""" """
# pylint: disable=protected-access,too-many-public-methods # pylint: disable=protected-access,too-many-public-methods
import time from datetime import timedelta
import os import os
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
from datetime import timedelta
import homeassistant.core as ha import homeassistant.core as ha
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -25,21 +24,24 @@ class TestComponentHistory(unittest.TestCase):
def setUp(self): # pylint: disable=invalid-name def setUp(self): # pylint: disable=invalid-name
""" Init needed objects. """ """ Init needed objects. """
self.hass = get_test_home_assistant(1) self.hass = get_test_home_assistant(1)
self.init_rec = False
def tearDown(self): # pylint: disable=invalid-name def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """ """ Stop down stuff we started. """
self.hass.stop() self.hass.stop()
if self.init_rec: db_path = self.hass.config.path(recorder.DB_FILE)
recorder._INSTANCE.block_till_done() if os.path.isfile(db_path):
os.remove(self.hass.config.path(recorder.DB_FILE)) os.remove(db_path)
def init_recorder(self): def init_recorder(self):
recorder.setup(self.hass, {}) recorder.setup(self.hass, {})
self.hass.start() self.hass.start()
self.wait_recording_done()
def wait_recording_done(self):
""" Block till recording is done. """
self.hass.pool.block_till_done()
recorder._INSTANCE.block_till_done() recorder._INSTANCE.block_till_done()
self.init_rec = True
def test_setup(self): def test_setup(self):
""" Test setup method of history. """ """ Test setup method of history. """
@ -56,12 +58,11 @@ class TestComponentHistory(unittest.TestCase):
for i in range(7): for i in range(7):
self.hass.states.set(entity_id, "State {}".format(i)) self.hass.states.set(entity_id, "State {}".format(i))
self.wait_recording_done()
if i > 1: if i > 1:
states.append(self.hass.states.get(entity_id)) states.append(self.hass.states.get(entity_id))
self.hass.pool.block_till_done()
recorder._INSTANCE.block_till_done()
self.assertEqual( self.assertEqual(
list(reversed(states)), history.last_5_states(entity_id)) list(reversed(states)), history.last_5_states(entity_id))
@ -70,22 +71,9 @@ class TestComponentHistory(unittest.TestCase):
self.init_recorder() self.init_recorder()
states = [] states = []
for i in range(5): now = dt_util.utcnow()
state = ha.State( with patch('homeassistant.components.recorder.dt_util.utcnow',
'test.point_in_time_{}'.format(i % 5), return_value=now):
"State {}".format(i),
{'attribute_test': i})
mock_state_change_event(self.hass, state)
self.hass.pool.block_till_done()
states.append(state)
recorder._INSTANCE.block_till_done()
point = dt_util.utcnow() + timedelta(seconds=1)
with patch('homeassistant.util.dt.utcnow', return_value=point):
for i in range(5): for i in range(5):
state = ha.State( state = ha.State(
'test.point_in_time_{}'.format(i % 5), 'test.point_in_time_{}'.format(i % 5),
@ -93,16 +81,32 @@ class TestComponentHistory(unittest.TestCase):
{'attribute_test': i}) {'attribute_test': i})
mock_state_change_event(self.hass, state) mock_state_change_event(self.hass, state)
self.hass.pool.block_till_done()
states.append(state)
self.wait_recording_done()
future = now + timedelta(seconds=1)
with patch('homeassistant.components.recorder.dt_util.utcnow',
return_value=future):
for i in range(5):
state = ha.State(
'test.point_in_time_{}'.format(i % 5),
"State {}".format(i),
{'attribute_test': i})
mock_state_change_event(self.hass, state)
self.wait_recording_done()
# Get states returns everything before POINT # Get states returns everything before POINT
self.assertEqual(states, self.assertEqual(states,
sorted(history.get_states(point), sorted(history.get_states(future),
key=lambda state: state.entity_id)) key=lambda state: state.entity_id))
# Test get_state here because we have a DB setup # Test get_state here because we have a DB setup
self.assertEqual( self.assertEqual(
states[0], history.get_state(point, states[0].entity_id)) states[0], history.get_state(future, states[0].entity_id))
def test_state_changes_during_period(self): def test_state_changes_during_period(self):
self.init_recorder() self.init_recorder()
@ -110,19 +114,20 @@ class TestComponentHistory(unittest.TestCase):
def set_state(state): def set_state(state):
self.hass.states.set(entity_id, state) self.hass.states.set(entity_id, state)
self.hass.pool.block_till_done() self.wait_recording_done()
recorder._INSTANCE.block_till_done()
return self.hass.states.get(entity_id) return self.hass.states.get(entity_id)
set_state('idle')
set_state('YouTube')
start = dt_util.utcnow() start = dt_util.utcnow()
point = start + timedelta(seconds=1) point = start + timedelta(seconds=1)
end = point + timedelta(seconds=1) end = point + timedelta(seconds=1)
with patch('homeassistant.util.dt.utcnow', return_value=point): with patch('homeassistant.components.recorder.dt_util.utcnow',
return_value=start):
set_state('idle')
set_state('YouTube')
with patch('homeassistant.components.recorder.dt_util.utcnow',
return_value=point):
states = [ states = [
set_state('idle'), set_state('idle'),
set_state('Netflix'), set_state('Netflix'),
@ -130,10 +135,11 @@ class TestComponentHistory(unittest.TestCase):
set_state('YouTube'), set_state('YouTube'),
] ]
with patch('homeassistant.util.dt.utcnow', return_value=end): with patch('homeassistant.components.recorder.dt_util.utcnow',
return_value=end):
set_state('Netflix') set_state('Netflix')
set_state('Plex') set_state('Plex')
self.assertEqual( hist = history.state_changes_during_period(start, end, entity_id)
{entity_id: states},
history.state_changes_during_period(start, end, entity_id)) self.assertEqual(states, hist[entity_id])