mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 09:47:52 +00:00
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:
parent
0aa22d9d91
commit
c4810da82f
@ -116,6 +116,6 @@ class AsyncHandler(object):
|
||||
return self.handler.get_name()
|
||||
|
||||
@name.setter
|
||||
def set_name(self, name):
|
||||
def name(self, name):
|
||||
"""Wrap property get_name to handler."""
|
||||
self.handler.name = name
|
||||
|
@ -51,6 +51,85 @@ def test_caching_data(hass):
|
||||
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):
|
||||
"""Add test data in the last recorder_run."""
|
||||
# pylint: disable=protected-access
|
||||
|
@ -8,52 +8,74 @@ import pytest
|
||||
from homeassistant.util import async as hasync
|
||||
|
||||
|
||||
@patch('asyncio.coroutines.iscoroutine', return_value=True)
|
||||
@patch('asyncio.coroutines.iscoroutine')
|
||||
@patch('concurrent.futures.Future')
|
||||
@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."""
|
||||
coro = MagicMock()
|
||||
loop = MagicMock()
|
||||
|
||||
loop._thread_ident = None
|
||||
mock_ident.return_value = 5
|
||||
mock_iscoroutine.return_value = True
|
||||
hasync.run_coroutine_threadsafe(coro, loop)
|
||||
assert len(loop.call_soon_threadsafe.mock_calls) == 1
|
||||
|
||||
loop._thread_ident = 5
|
||||
mock_ident.return_value = 5
|
||||
mock_iscoroutine.return_value = True
|
||||
with pytest.raises(RuntimeError):
|
||||
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 = 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)
|
||||
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('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."""
|
||||
coro = MagicMock()
|
||||
loop = MagicMock()
|
||||
|
||||
loop._thread_ident = None
|
||||
mock_ident.return_value = 5
|
||||
mock_iscoroutine.return_value = True
|
||||
hasync.fire_coroutine_threadsafe(coro, loop)
|
||||
assert len(loop.call_soon_threadsafe.mock_calls) == 1
|
||||
|
||||
loop._thread_ident = 5
|
||||
mock_ident.return_value = 5
|
||||
mock_iscoroutine.return_value = True
|
||||
with pytest.raises(RuntimeError):
|
||||
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 = 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)
|
||||
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
|
||||
|
||||
|
||||
class RunCoroutineThreadsafeTests(test_utils.TestCase):
|
||||
class RunThreadsafeTests(test_utils.TestCase):
|
||||
"""Test case for asyncio.run_coroutine_threadsafe."""
|
||||
|
||||
def setUp(self):
|
||||
@ -91,26 +113,41 @@ class RunCoroutineThreadsafeTests(test_utils.TestCase):
|
||||
self.loop = asyncio.new_event_loop()
|
||||
self.set_event_loop(self.loop) # Will cleanup properly
|
||||
|
||||
@asyncio.coroutine
|
||||
def add(self, a, b, fail=False, cancel=False):
|
||||
"""Wait 0.05 second and return a + b."""
|
||||
yield from asyncio.sleep(0.05, loop=self.loop)
|
||||
def add_callback(self, a, b, fail, invalid):
|
||||
"""Return a + b."""
|
||||
if 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:
|
||||
asyncio.tasks.Task.current_task(self.loop).cancel()
|
||||
yield
|
||||
return a + b
|
||||
return self.add_callback(a, b, fail, invalid)
|
||||
|
||||
def target(self, fail=False, cancel=False, timeout=None,
|
||||
advance_coro=False):
|
||||
def target_callback(self, fail=False, invalid=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."""
|
||||
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)
|
||||
if advance_coro:
|
||||
# this is for test_run_coroutine_threadsafe_task_factory_exception;
|
||||
# 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
|
||||
# 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):
|
||||
"""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)
|
||||
self.assertEqual(result, 3)
|
||||
|
||||
def test_run_coroutine_threadsafe_with_exception(self):
|
||||
"""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:
|
||||
self.loop.run_until_complete(future)
|
||||
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):
|
||||
"""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)
|
||||
with self.assertRaises(asyncio.TimeoutError):
|
||||
self.loop.run_until_complete(future)
|
||||
@ -147,7 +192,28 @@ class RunCoroutineThreadsafeTests(test_utils.TestCase):
|
||||
|
||||
def test_run_coroutine_threadsafe_task_cancelled(self):
|
||||
"""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)
|
||||
with self.assertRaises(asyncio.CancelledError):
|
||||
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)
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Test Home Assistant util methods."""
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import patch, MagicMock
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from homeassistant import util
|
||||
@ -266,3 +266,17 @@ class TestUtil(unittest.TestCase):
|
||||
|
||||
self.assertTrue(tester.hello())
|
||||
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'
|
||||
|
68
tests/util/test_logging.py
Normal file
68
tests/util/test_logging.py
Normal 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()
|
Loading…
x
Reference in New Issue
Block a user