Unit tests to improve core coverage (#9659)

* Code coverage of logging util

* Improve async util coverage

* Add test coverage for restore_state

* get_random_string test
This commit is contained in:
Adam Mills 2017-10-02 23:25:04 -04:00 committed by Paulus Schoutsen
parent 0aa22d9d91
commit c4810da82f
5 changed files with 247 additions and 20 deletions

View File

@ -116,6 +116,6 @@ class AsyncHandler(object):
return self.handler.get_name() return self.handler.get_name()
@name.setter @name.setter
def set_name(self, name): def name(self, name):
"""Wrap property get_name to handler.""" """Wrap property get_name to handler."""
self.handler.name = name self.handler.name = name

View File

@ -51,6 +51,85 @@ def test_caching_data(hass):
assert DATA_RESTORE_CACHE not in hass.data assert DATA_RESTORE_CACHE not in hass.data
@asyncio.coroutine
def test_hass_running(hass):
"""Test that cache cannot be accessed while hass is running."""
mock_component(hass, 'recorder')
states = [
State('input_boolean.b0', 'on'),
State('input_boolean.b1', 'on'),
State('input_boolean.b2', 'on'),
]
with patch('homeassistant.helpers.restore_state.last_recorder_run',
return_value=MagicMock(end=dt_util.utcnow())), \
patch('homeassistant.helpers.restore_state.get_states',
return_value=states), \
patch('homeassistant.helpers.restore_state.wait_connection_ready',
return_value=mock_coro(True)):
state = yield from async_get_last_state(hass, 'input_boolean.b1')
assert state is None
@asyncio.coroutine
def test_not_connected(hass):
"""Test that cache cannot be accessed if db connection times out."""
mock_component(hass, 'recorder')
hass.state = CoreState.starting
states = [State('input_boolean.b1', 'on')]
with patch('homeassistant.helpers.restore_state.last_recorder_run',
return_value=MagicMock(end=dt_util.utcnow())), \
patch('homeassistant.helpers.restore_state.get_states',
return_value=states), \
patch('homeassistant.helpers.restore_state.wait_connection_ready',
return_value=mock_coro(False)):
state = yield from async_get_last_state(hass, 'input_boolean.b1')
assert state is None
@asyncio.coroutine
def test_no_last_run_found(hass):
"""Test that cache cannot be accessed if no last run found."""
mock_component(hass, 'recorder')
hass.state = CoreState.starting
states = [State('input_boolean.b1', 'on')]
with patch('homeassistant.helpers.restore_state.last_recorder_run',
return_value=None), \
patch('homeassistant.helpers.restore_state.get_states',
return_value=states), \
patch('homeassistant.helpers.restore_state.wait_connection_ready',
return_value=mock_coro(True)):
state = yield from async_get_last_state(hass, 'input_boolean.b1')
assert state is None
@asyncio.coroutine
def test_cache_timeout(hass):
"""Test that cache timeout returns none."""
mock_component(hass, 'recorder')
hass.state = CoreState.starting
states = [State('input_boolean.b1', 'on')]
@asyncio.coroutine
def timeout_coro():
raise asyncio.TimeoutError()
with patch('homeassistant.helpers.restore_state.last_recorder_run',
return_value=MagicMock(end=dt_util.utcnow())), \
patch('homeassistant.helpers.restore_state.get_states',
return_value=states), \
patch('homeassistant.helpers.restore_state.wait_connection_ready',
return_value=timeout_coro()):
state = yield from async_get_last_state(hass, 'input_boolean.b1')
assert state is None
def _add_data_in_last_run(hass, entities): def _add_data_in_last_run(hass, entities):
"""Add test data in the last recorder_run.""" """Add test data in the last recorder_run."""
# pylint: disable=protected-access # pylint: disable=protected-access

View File

@ -8,52 +8,74 @@ import pytest
from homeassistant.util import async as hasync from homeassistant.util import async as hasync
@patch('asyncio.coroutines.iscoroutine', return_value=True) @patch('asyncio.coroutines.iscoroutine')
@patch('concurrent.futures.Future') @patch('concurrent.futures.Future')
@patch('threading.get_ident') @patch('threading.get_ident')
def test_run_coroutine_threadsafe_from_inside_event_loop(mock_ident, _, __): def test_run_coroutine_threadsafe_from_inside_event_loop(
mock_ident, _, mock_iscoroutine):
"""Testing calling run_coroutine_threadsafe from inside an event loop.""" """Testing calling run_coroutine_threadsafe from inside an event loop."""
coro = MagicMock() coro = MagicMock()
loop = MagicMock() loop = MagicMock()
loop._thread_ident = None loop._thread_ident = None
mock_ident.return_value = 5 mock_ident.return_value = 5
mock_iscoroutine.return_value = True
hasync.run_coroutine_threadsafe(coro, loop) hasync.run_coroutine_threadsafe(coro, loop)
assert len(loop.call_soon_threadsafe.mock_calls) == 1 assert len(loop.call_soon_threadsafe.mock_calls) == 1
loop._thread_ident = 5 loop._thread_ident = 5
mock_ident.return_value = 5 mock_ident.return_value = 5
mock_iscoroutine.return_value = True
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
hasync.run_coroutine_threadsafe(coro, loop) hasync.run_coroutine_threadsafe(coro, loop)
assert len(loop.call_soon_threadsafe.mock_calls) == 1 assert len(loop.call_soon_threadsafe.mock_calls) == 1
loop._thread_ident = 1 loop._thread_ident = 1
mock_ident.return_value = 5 mock_ident.return_value = 5
mock_iscoroutine.return_value = False
with pytest.raises(TypeError):
hasync.run_coroutine_threadsafe(coro, loop)
assert len(loop.call_soon_threadsafe.mock_calls) == 1
loop._thread_ident = 1
mock_ident.return_value = 5
mock_iscoroutine.return_value = True
hasync.run_coroutine_threadsafe(coro, loop) hasync.run_coroutine_threadsafe(coro, loop)
assert len(loop.call_soon_threadsafe.mock_calls) == 2 assert len(loop.call_soon_threadsafe.mock_calls) == 2
@patch('asyncio.coroutines.iscoroutine', return_value=True) @patch('asyncio.coroutines.iscoroutine')
@patch('concurrent.futures.Future') @patch('concurrent.futures.Future')
@patch('threading.get_ident') @patch('threading.get_ident')
def test_fire_coroutine_threadsafe_from_inside_event_loop(mock_ident, _, __): def test_fire_coroutine_threadsafe_from_inside_event_loop(
mock_ident, _, mock_iscoroutine):
"""Testing calling fire_coroutine_threadsafe from inside an event loop.""" """Testing calling fire_coroutine_threadsafe from inside an event loop."""
coro = MagicMock() coro = MagicMock()
loop = MagicMock() loop = MagicMock()
loop._thread_ident = None loop._thread_ident = None
mock_ident.return_value = 5 mock_ident.return_value = 5
mock_iscoroutine.return_value = True
hasync.fire_coroutine_threadsafe(coro, loop) hasync.fire_coroutine_threadsafe(coro, loop)
assert len(loop.call_soon_threadsafe.mock_calls) == 1 assert len(loop.call_soon_threadsafe.mock_calls) == 1
loop._thread_ident = 5 loop._thread_ident = 5
mock_ident.return_value = 5 mock_ident.return_value = 5
mock_iscoroutine.return_value = True
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
hasync.fire_coroutine_threadsafe(coro, loop) hasync.fire_coroutine_threadsafe(coro, loop)
assert len(loop.call_soon_threadsafe.mock_calls) == 1 assert len(loop.call_soon_threadsafe.mock_calls) == 1
loop._thread_ident = 1 loop._thread_ident = 1
mock_ident.return_value = 5 mock_ident.return_value = 5
mock_iscoroutine.return_value = False
with pytest.raises(TypeError):
hasync.fire_coroutine_threadsafe(coro, loop)
assert len(loop.call_soon_threadsafe.mock_calls) == 1
loop._thread_ident = 1
mock_ident.return_value = 5
mock_iscoroutine.return_value = True
hasync.fire_coroutine_threadsafe(coro, loop) hasync.fire_coroutine_threadsafe(coro, loop)
assert len(loop.call_soon_threadsafe.mock_calls) == 2 assert len(loop.call_soon_threadsafe.mock_calls) == 2
@ -82,7 +104,7 @@ def test_run_callback_threadsafe_from_inside_event_loop(mock_ident, _):
assert len(loop.call_soon_threadsafe.mock_calls) == 2 assert len(loop.call_soon_threadsafe.mock_calls) == 2
class RunCoroutineThreadsafeTests(test_utils.TestCase): class RunThreadsafeTests(test_utils.TestCase):
"""Test case for asyncio.run_coroutine_threadsafe.""" """Test case for asyncio.run_coroutine_threadsafe."""
def setUp(self): def setUp(self):
@ -91,26 +113,41 @@ class RunCoroutineThreadsafeTests(test_utils.TestCase):
self.loop = asyncio.new_event_loop() self.loop = asyncio.new_event_loop()
self.set_event_loop(self.loop) # Will cleanup properly self.set_event_loop(self.loop) # Will cleanup properly
@asyncio.coroutine def add_callback(self, a, b, fail, invalid):
def add(self, a, b, fail=False, cancel=False): """Return a + b."""
"""Wait 0.05 second and return a + b."""
yield from asyncio.sleep(0.05, loop=self.loop)
if fail: if fail:
raise RuntimeError("Fail!") raise RuntimeError("Fail!")
if invalid:
raise ValueError("Invalid!")
return a + b
@asyncio.coroutine
def add_coroutine(self, a, b, fail, invalid, cancel):
"""Wait 0.05 second and return a + b."""
yield from asyncio.sleep(0.05, loop=self.loop)
if cancel: if cancel:
asyncio.tasks.Task.current_task(self.loop).cancel() asyncio.tasks.Task.current_task(self.loop).cancel()
yield yield
return a + b return self.add_callback(a, b, fail, invalid)
def target(self, fail=False, cancel=False, timeout=None, def target_callback(self, fail=False, invalid=False):
advance_coro=False): """Run add callback in the event loop."""
future = hasync.run_callback_threadsafe(
self.loop, self.add_callback, 1, 2, fail, invalid)
try:
return future.result()
finally:
future.done() or future.cancel()
def target_coroutine(self, fail=False, invalid=False, cancel=False,
timeout=None, advance_coro=False):
"""Run add coroutine in the event loop.""" """Run add coroutine in the event loop."""
coro = self.add(1, 2, fail=fail, cancel=cancel) coro = self.add_coroutine(1, 2, fail, invalid, cancel)
future = hasync.run_coroutine_threadsafe(coro, self.loop) future = hasync.run_coroutine_threadsafe(coro, self.loop)
if advance_coro: if advance_coro:
# this is for test_run_coroutine_threadsafe_task_factory_exception; # this is for test_run_coroutine_threadsafe_task_factory_exception;
# otherwise it spills errors and breaks **other** unittests, since # otherwise it spills errors and breaks **other** unittests, since
# 'target' is interacting with threads. # 'target_coroutine' is interacting with threads.
# With this call, `coro` will be advanced, so that # With this call, `coro` will be advanced, so that
# CoroWrapper.__del__ won't do anything when asyncio tests run # CoroWrapper.__del__ won't do anything when asyncio tests run
@ -123,20 +160,28 @@ class RunCoroutineThreadsafeTests(test_utils.TestCase):
def test_run_coroutine_threadsafe(self): def test_run_coroutine_threadsafe(self):
"""Test coroutine submission from a thread to an event loop.""" """Test coroutine submission from a thread to an event loop."""
future = self.loop.run_in_executor(None, self.target) future = self.loop.run_in_executor(None, self.target_coroutine)
result = self.loop.run_until_complete(future) result = self.loop.run_until_complete(future)
self.assertEqual(result, 3) self.assertEqual(result, 3)
def test_run_coroutine_threadsafe_with_exception(self): def test_run_coroutine_threadsafe_with_exception(self):
"""Test coroutine submission from thread to event loop on exception.""" """Test coroutine submission from thread to event loop on exception."""
future = self.loop.run_in_executor(None, self.target, True) future = self.loop.run_in_executor(None, self.target_coroutine, True)
with self.assertRaises(RuntimeError) as exc_context: with self.assertRaises(RuntimeError) as exc_context:
self.loop.run_until_complete(future) self.loop.run_until_complete(future)
self.assertIn("Fail!", exc_context.exception.args) self.assertIn("Fail!", exc_context.exception.args)
def test_run_coroutine_threadsafe_with_invalid(self):
"""Test coroutine submission from thread to event loop on invalid."""
callback = lambda: self.target_coroutine(invalid=True) # noqa
future = self.loop.run_in_executor(None, callback)
with self.assertRaises(ValueError) as exc_context:
self.loop.run_until_complete(future)
self.assertIn("Invalid!", exc_context.exception.args)
def test_run_coroutine_threadsafe_with_timeout(self): def test_run_coroutine_threadsafe_with_timeout(self):
"""Test coroutine submission from thread to event loop on timeout.""" """Test coroutine submission from thread to event loop on timeout."""
callback = lambda: self.target(timeout=0) # noqa callback = lambda: self.target_coroutine(timeout=0) # noqa
future = self.loop.run_in_executor(None, callback) future = self.loop.run_in_executor(None, callback)
with self.assertRaises(asyncio.TimeoutError): with self.assertRaises(asyncio.TimeoutError):
self.loop.run_until_complete(future) self.loop.run_until_complete(future)
@ -147,7 +192,28 @@ class RunCoroutineThreadsafeTests(test_utils.TestCase):
def test_run_coroutine_threadsafe_task_cancelled(self): def test_run_coroutine_threadsafe_task_cancelled(self):
"""Test coroutine submission from tread to event loop on cancel.""" """Test coroutine submission from tread to event loop on cancel."""
callback = lambda: self.target(cancel=True) # noqa callback = lambda: self.target_coroutine(cancel=True) # noqa
future = self.loop.run_in_executor(None, callback) future = self.loop.run_in_executor(None, callback)
with self.assertRaises(asyncio.CancelledError): with self.assertRaises(asyncio.CancelledError):
self.loop.run_until_complete(future) self.loop.run_until_complete(future)
def test_run_callback_threadsafe(self):
"""Test callback submission from a thread to an event loop."""
future = self.loop.run_in_executor(None, self.target_callback)
result = self.loop.run_until_complete(future)
self.assertEqual(result, 3)
def test_run_callback_threadsafe_with_exception(self):
"""Test callback submission from thread to event loop on exception."""
future = self.loop.run_in_executor(None, self.target_callback, True)
with self.assertRaises(RuntimeError) as exc_context:
self.loop.run_until_complete(future)
self.assertIn("Fail!", exc_context.exception.args)
def test_run_callback_threadsafe_with_invalid(self):
"""Test callback submission from thread to event loop on invalid."""
callback = lambda: self.target_callback(invalid=True) # noqa
future = self.loop.run_in_executor(None, callback)
with self.assertRaises(ValueError) as exc_context:
self.loop.run_until_complete(future)
self.assertIn("Invalid!", exc_context.exception.args)

View File

@ -1,6 +1,6 @@
"""Test Home Assistant util methods.""" """Test Home Assistant util methods."""
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch, MagicMock
from datetime import datetime, timedelta from datetime import datetime, timedelta
from homeassistant import util from homeassistant import util
@ -266,3 +266,17 @@ class TestUtil(unittest.TestCase):
self.assertTrue(tester.hello()) self.assertTrue(tester.hello())
self.assertTrue(tester.goodbye()) self.assertTrue(tester.goodbye())
@patch.object(util, 'random')
def test_get_random_string(self, mock_random):
"""Test get random string."""
results = ['A', 'B', 'C']
def mock_choice(choices):
return results.pop(0)
generator = MagicMock()
generator.choice.side_effect = mock_choice
mock_random.SystemRandom.return_value = generator
assert util.get_random_string(length=3) == 'ABC'

View File

@ -0,0 +1,68 @@
"""Test Home Assistant logging util methods."""
import asyncio
import logging
import threading
import homeassistant.util.logging as logging_util
@asyncio.coroutine
def test_sensitive_data_filter():
"""Test the logging sensitive data filter."""
log_filter = logging_util.HideSensitiveDataFilter('mock_sensitive')
clean_record = logging.makeLogRecord({'msg': "clean log data"})
log_filter.filter(clean_record)
assert clean_record.msg == "clean log data"
sensitive_record = logging.makeLogRecord({'msg': "mock_sensitive log"})
log_filter.filter(sensitive_record)
assert sensitive_record.msg == "******* log"
@asyncio.coroutine
def test_async_handler_loop_log(loop):
"""Test the logging sensitive data filter."""
loop._thread_ident = threading.get_ident()
queue = asyncio.Queue(loop=loop)
base_handler = logging.handlers.QueueHandler(queue)
handler = logging_util.AsyncHandler(loop, base_handler)
# Test passthrough props and noop functions
assert handler.createLock() is None
assert handler.acquire() is None
assert handler.release() is None
assert handler.formatter is base_handler.formatter
assert handler.name is base_handler.get_name()
handler.name = 'mock_name'
assert base_handler.get_name() == 'mock_name'
log_record = logging.makeLogRecord({'msg': "Test Log Record"})
handler.emit(log_record)
yield from handler.async_close(True)
assert queue.get_nowait() == log_record
assert queue.empty()
@asyncio.coroutine
def test_async_handler_thread_log(loop):
"""Test the logging sensitive data filter."""
loop._thread_ident = threading.get_ident()
queue = asyncio.Queue(loop=loop)
base_handler = logging.handlers.QueueHandler(queue)
handler = logging_util.AsyncHandler(loop, base_handler)
log_record = logging.makeLogRecord({'msg': "Test Log Record"})
def add_log():
"""Emit a mock log."""
handler.emit(log_record)
handler.close()
yield from loop.run_in_executor(None, add_log)
yield from handler.async_close(True)
assert queue.get_nowait() == log_record
assert queue.empty()