Merge pull request #55673 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2021-09-03 10:53:16 -07:00 committed by GitHub
commit 33047d7260
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 259 additions and 96 deletions

View File

@ -342,7 +342,11 @@ def async_enable_logging(
err_log_path, backupCount=1 err_log_path, backupCount=1
) )
err_handler.doRollover() try:
err_handler.doRollover()
except OSError as err:
_LOGGER.error("Error rolling over log file: %s", err)
err_handler.setLevel(logging.INFO if verbose else logging.WARNING) err_handler.setLevel(logging.INFO if verbose else logging.WARNING)
err_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt)) err_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt))

View File

@ -4,6 +4,7 @@ from __future__ import annotations
import asyncio import asyncio
from collections.abc import Iterable, Mapping from collections.abc import Iterable, Mapping
from functools import wraps from functools import wraps
import logging
from types import ModuleType from types import ModuleType
from typing import Any from typing import Any
@ -27,7 +28,6 @@ from .exceptions import DeviceNotFound, InvalidDeviceAutomationConfig
DOMAIN = "device_automation" DOMAIN = "device_automation"
DEVICE_TRIGGER_BASE_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( DEVICE_TRIGGER_BASE_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend(
{ {
vol.Required(CONF_PLATFORM): "device", vol.Required(CONF_PLATFORM): "device",
@ -174,6 +174,13 @@ async def _async_get_device_automations(
device_results, InvalidDeviceAutomationConfig device_results, InvalidDeviceAutomationConfig
): ):
continue continue
if isinstance(device_results, Exception):
logging.getLogger(__name__).error(
"Unexpected error fetching device %ss",
automation_type,
exc_info=device_results,
)
continue
for automation in device_results: for automation in device_results:
combined_results[automation["device_id"]].append(automation) combined_results[automation["device_id"]].append(automation)

View File

@ -7,6 +7,8 @@ from homeassistant.components.device_automation import (
) )
from homeassistant.const import CONF_DOMAIN from homeassistant.const import CONF_DOMAIN
from .exceptions import InvalidDeviceAutomationConfig
# mypy: allow-untyped-defs, no-check-untyped-defs # mypy: allow-untyped-defs, no-check-untyped-defs
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA) TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend({}, extra=vol.ALLOW_EXTRA)
@ -17,10 +19,13 @@ async def async_validate_trigger_config(hass, config):
platform = await async_get_device_automation_platform( platform = await async_get_device_automation_platform(
hass, config[CONF_DOMAIN], "trigger" hass, config[CONF_DOMAIN], "trigger"
) )
if hasattr(platform, "async_validate_trigger_config"): if not hasattr(platform, "async_validate_trigger_config"):
return await getattr(platform, "async_validate_trigger_config")(hass, config) return platform.TRIGGER_SCHEMA(config)
return platform.TRIGGER_SCHEMA(config) try:
return await getattr(platform, "async_validate_trigger_config")(hass, config)
except InvalidDeviceAutomationConfig as err:
raise vol.Invalid(str(err) or "Invalid trigger configuration") from err
async def async_attach_trigger(hass, config, action, automation_info): async def async_attach_trigger(hass, config, action, automation_info):

View File

@ -4,6 +4,7 @@ from __future__ import annotations
import logging import logging
from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.components.switch import DOMAIN, SwitchEntity
from homeassistant.const import STATE_OFF, STATE_ON
from . import ATTR_NEW, CecEntity from . import ATTR_NEW, CecEntity
@ -34,17 +35,25 @@ class CecSwitchEntity(CecEntity, SwitchEntity):
def turn_on(self, **kwargs) -> None: def turn_on(self, **kwargs) -> None:
"""Turn device on.""" """Turn device on."""
self._device.turn_on() self._device.turn_on()
self._attr_is_on = True self._state = STATE_ON
self.schedule_update_ha_state(force_refresh=False) self.schedule_update_ha_state(force_refresh=False)
def turn_off(self, **kwargs) -> None: def turn_off(self, **kwargs) -> None:
"""Turn device off.""" """Turn device off."""
self._device.turn_off() self._device.turn_off()
self._attr_is_on = False self._state = STATE_OFF
self.schedule_update_ha_state(force_refresh=False) self.schedule_update_ha_state(force_refresh=False)
def toggle(self, **kwargs): def toggle(self, **kwargs):
"""Toggle the entity.""" """Toggle the entity."""
self._device.toggle() self._device.toggle()
self._attr_is_on = not self._attr_is_on if self._state == STATE_ON:
self._state = STATE_OFF
else:
self._state = STATE_ON
self.schedule_update_ha_state(force_refresh=False) self.schedule_update_ha_state(force_refresh=False)
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
return self._state == STATE_ON

View File

@ -118,12 +118,16 @@ async def async_validate_trigger_config(hass, config):
trigger = (config[CONF_TYPE], config[CONF_SUBTYPE]) trigger = (config[CONF_TYPE], config[CONF_SUBTYPE])
if ( if not device:
not device raise InvalidDeviceAutomationConfig("Device {config[CONF_DEVICE_ID]} not found")
or device.model not in REMOTES
or trigger not in REMOTES[device.model] if device.model not in REMOTES:
): raise InvalidDeviceAutomationConfig(
raise InvalidDeviceAutomationConfig f"Device model {device.model} is not a remote"
)
if trigger not in REMOTES[device.model]:
raise InvalidDeviceAutomationConfig("Device does not support trigger {trigger}")
return config return config

View File

@ -32,6 +32,8 @@ from .const import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SENSOR_KEYS = [desc.key for desc in SENSOR_TYPES]
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
vol.All( vol.All(
# Deprecated in Home Assistant 2021.6 # Deprecated in Home Assistant 2021.6
@ -46,8 +48,8 @@ CONFIG_SCHEMA = vol.Schema(
): cv.positive_time_period, ): cv.positive_time_period,
vol.Optional(CONF_MANUAL, default=False): cv.boolean, vol.Optional(CONF_MANUAL, default=False): cv.boolean,
vol.Optional( vol.Optional(
CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES) CONF_MONITORED_CONDITIONS, default=list(SENSOR_KEYS)
): vol.All(cv.ensure_list, [vol.In(list(SENSOR_TYPES))]), ): vol.All(cv.ensure_list, [vol.In(list(SENSOR_KEYS))]),
} }
) )
}, },

View File

@ -289,7 +289,7 @@ class Scanner:
def _async_unsee(self, header_st: str | None, header_location: str | None) -> None: def _async_unsee(self, header_st: str | None, header_location: str | None) -> None:
"""If we see a device in a new location, unsee the original location.""" """If we see a device in a new location, unsee the original location."""
if header_st is not None: if header_st is not None:
self.seen.remove((header_st, header_location)) self.seen.discard((header_st, header_location))
async def _async_process_entry(self, headers: Mapping[str, str]) -> None: async def _async_process_entry(self, headers: Mapping[str, str]) -> None:
"""Process SSDP entries.""" """Process SSDP entries."""

View File

@ -90,7 +90,8 @@ async def async_setup_entry(hass, entry, async_add_entities):
sensor sensor
for device in account.api.devices.values() for device in account.api.devices.values()
for description in SENSOR_TYPES for description in SENSOR_TYPES
if (sensor := StarlineSensor(account, device, description)).state is not None if (sensor := StarlineSensor(account, device, description)).native_value
is not None
] ]
async_add_entities(entities) async_add_entities(entities)

View File

@ -69,7 +69,7 @@ class TriggerEntity(update_coordinator.CoordinatorEntity):
# We make a copy so our initial render is 'unknown' and not 'unavailable' # We make a copy so our initial render is 'unknown' and not 'unavailable'
self._rendered = dict(self._static_rendered) self._rendered = dict(self._static_rendered)
self._parse_result = set() self._parse_result = {CONF_AVAILABILITY}
@property @property
def name(self): def name(self):

View File

@ -112,7 +112,7 @@ class USBDiscovery:
if not sys.platform.startswith("linux"): if not sys.platform.startswith("linux"):
return return
info = await system_info.async_get_system_info(self.hass) info = await system_info.async_get_system_info(self.hass)
if info.get("docker") and not info.get("hassio"): if info.get("docker"):
return return
from pyudev import ( # pylint: disable=import-outside-toplevel from pyudev import ( # pylint: disable=import-outside-toplevel

View File

@ -326,11 +326,6 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN):
device = discovery_info["device"] device = discovery_info["device"]
manufacturer = discovery_info["manufacturer"] manufacturer = discovery_info["manufacturer"]
description = discovery_info["description"] description = discovery_info["description"]
# The Nortek sticks are a special case since they
# have a Z-Wave and a Zigbee radio. We need to reject
# the Zigbee radio.
if vid == "10C4" and pid == "8A2A" and "Z-Wave" not in description:
return self.async_abort(reason="not_zwave_device")
# Zooz uses this vid/pid, but so do 2652 sticks # Zooz uses this vid/pid, but so do 2652 sticks
if vid == "10C4" and pid == "EA60" and "2652" in description: if vid == "10C4" and pid == "EA60" and "2652" in description:
return self.async_abort(reason="not_zwave_device") return self.async_abort(reason="not_zwave_device")

View File

@ -9,7 +9,7 @@
"iot_class": "local_push", "iot_class": "local_push",
"usb": [ "usb": [
{"vid":"0658","pid":"0200","known_devices":["Aeotec Z-Stick Gen5+", "Z-WaveMe UZB"]}, {"vid":"0658","pid":"0200","known_devices":["Aeotec Z-Stick Gen5+", "Z-WaveMe UZB"]},
{"vid":"10C4","pid":"8A2A","known_devices":["Nortek HUSBZB-1"]}, {"vid":"10C4","pid":"8A2A","description":"*z-wave*","known_devices":["Nortek HUSBZB-1"]},
{"vid":"10C4","pid":"EA60","known_devices":["Aeotec Z-Stick 7", "Silicon Labs UZB-7", "Zooz ZST10 700"]} {"vid":"10C4","pid":"EA60","known_devices":["Aeotec Z-Stick 7", "Silicon Labs UZB-7", "Zooz ZST10 700"]}
] ]
} }

View File

@ -5,7 +5,7 @@ from typing import Final
MAJOR_VERSION: Final = 2021 MAJOR_VERSION: Final = 2021
MINOR_VERSION: Final = 9 MINOR_VERSION: Final = 9
PATCH_VERSION: Final = "1" PATCH_VERSION: Final = "2"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0) REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)

View File

@ -32,7 +32,8 @@ USB = [
{ {
"domain": "zwave_js", "domain": "zwave_js",
"vid": "10C4", "vid": "10C4",
"pid": "8A2A" "pid": "8A2A",
"description": "*z-wave*"
}, },
{ {
"domain": "zwave_js", "domain": "zwave_js",

View File

@ -14,6 +14,7 @@ from unittest.mock import patch
from homeassistant import core from homeassistant import core
from homeassistant.config import get_default_config_dir from homeassistant.config import get_default_config_dir
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import area_registry, device_registry, entity_registry
from homeassistant.helpers.check_config import async_check_ha_config_file from homeassistant.helpers.check_config import async_check_ha_config_file
from homeassistant.util.yaml import Secrets from homeassistant.util.yaml import Secrets
import homeassistant.util.yaml.loader as yaml_loader import homeassistant.util.yaml.loader as yaml_loader
@ -229,6 +230,9 @@ async def async_check_config(config_dir):
"""Check the HA config.""" """Check the HA config."""
hass = core.HomeAssistant() hass = core.HomeAssistant()
hass.config.config_dir = config_dir hass.config.config_dir = config_dir
await area_registry.async_load(hass)
await device_registry.async_load(hass)
await entity_registry.async_load(hass)
components = await async_check_ha_config_file(hass) components = await async_check_ha_config_file(hass)
await hass.async_stop(force=True) await hass.async_stop(force=True)
return components return components

View File

@ -1,4 +1,6 @@
"""The test for light device automation.""" """The test for light device automation."""
from unittest.mock import patch
import pytest import pytest
from homeassistant.components import device_automation from homeassistant.components import device_automation
@ -443,6 +445,28 @@ async def test_async_get_device_automations_all_devices_action(
assert len(result[device_entry.id]) == 3 assert len(result[device_entry.id]) == 3
async def test_async_get_device_automations_all_devices_action_exception_throw(
hass, device_reg, entity_reg, caplog
):
"""Test we get can fetch all the actions when no device id is passed and can handle one throwing an exception."""
await async_setup_component(hass, "device_automation", {})
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
device_entry = device_reg.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
entity_reg.async_get_or_create("light", "test", "5678", device_id=device_entry.id)
with patch(
"homeassistant.components.light.device_trigger.async_get_triggers",
side_effect=KeyError,
):
result = await device_automation.async_get_device_automations(hass, "trigger")
assert device_entry.id in result
assert len(result[device_entry.id]) == 0
assert "KeyError" in caplog.text
async def test_websocket_get_trigger_capabilities( async def test_websocket_get_trigger_capabilities(
hass, hass_ws_client, device_reg, entity_reg hass, hass_ws_client, device_reg, entity_reg
): ):

View File

@ -991,9 +991,6 @@ async def test_location_change_evicts_prior_location_from_cache(hass, aioclient_
@callback @callback
def _callback(*_): def _callback(*_):
import pprint
pprint.pprint(mock_ssdp_response)
hass.async_create_task(listener.async_callback(mock_ssdp_response)) hass.async_create_task(listener.async_callback(mock_ssdp_response))
listener.async_start = _async_callback listener.async_start = _async_callback
@ -1050,3 +1047,113 @@ async def test_location_change_evicts_prior_location_from_cache(hass, aioclient_
mock_init.mock_calls[0][2]["data"][ssdp.ATTR_SSDP_LOCATION] mock_init.mock_calls[0][2]["data"][ssdp.ATTR_SSDP_LOCATION]
== mock_good_ip_ssdp_response["location"] == mock_good_ip_ssdp_response["location"]
) )
async def test_location_change_with_overlapping_udn_st_combinations(
hass, aioclient_mock
):
"""Test handling when a UDN and ST broadcast multiple locations."""
mock_get_ssdp = {
"test_integration": [
{"manufacturer": "test_manufacturer", "modelName": "test_model"}
]
}
hue_response = """
<root xmlns="urn:schemas-upnp-org:device-1-0">
<device>
<manufacturer>test_manufacturer</manufacturer>
<modelName>test_model</modelName>
</device>
</root>
"""
aioclient_mock.get(
"http://192.168.72.1:49154/wps_device.xml",
text=hue_response.format(ip_address="192.168.72.1"),
)
aioclient_mock.get(
"http://192.168.72.1:49152/wps_device.xml",
text=hue_response.format(ip_address="192.168.72.1"),
)
ssdp_response_without_location = {
"ST": "upnp:rootdevice",
"_udn": "uuid:a793d3cc-e802-44fb-84f4-5a30f33115b6",
"USN": "uuid:a793d3cc-e802-44fb-84f4-5a30f33115b6::upnp:rootdevice",
"EXT": "",
}
port_49154_response = CaseInsensitiveDict(
**ssdp_response_without_location,
**{"LOCATION": "http://192.168.72.1:49154/wps_device.xml"},
)
port_49152_response = CaseInsensitiveDict(
**ssdp_response_without_location,
**{"LOCATION": "http://192.168.72.1:49152/wps_device.xml"},
)
mock_ssdp_response = port_49154_response
def _generate_fake_ssdp_listener(*args, **kwargs):
listener = SSDPListener(*args, **kwargs)
async def _async_callback(*_):
pass
@callback
def _callback(*_):
hass.async_create_task(listener.async_callback(mock_ssdp_response))
listener.async_start = _async_callback
listener.async_search = _callback
return listener
with patch(
"homeassistant.components.ssdp.async_get_ssdp",
return_value=mock_get_ssdp,
), patch(
"homeassistant.components.ssdp.SSDPListener",
new=_generate_fake_ssdp_listener,
), patch.object(
hass.config_entries.flow, "async_init"
) as mock_init:
assert await async_setup_component(hass, ssdp.DOMAIN, {ssdp.DOMAIN: {}})
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=200))
await hass.async_block_till_done()
assert len(mock_init.mock_calls) == 1
assert mock_init.mock_calls[0][1][0] == "test_integration"
assert mock_init.mock_calls[0][2]["context"] == {
"source": config_entries.SOURCE_SSDP
}
assert (
mock_init.mock_calls[0][2]["data"][ssdp.ATTR_SSDP_LOCATION]
== port_49154_response["location"]
)
mock_init.reset_mock()
mock_ssdp_response = port_49152_response
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=400))
await hass.async_block_till_done()
assert mock_init.mock_calls[0][1][0] == "test_integration"
assert mock_init.mock_calls[0][2]["context"] == {
"source": config_entries.SOURCE_SSDP
}
assert (
mock_init.mock_calls[0][2]["data"][ssdp.ATTR_SSDP_LOCATION]
== port_49152_response["location"]
)
mock_init.reset_mock()
mock_ssdp_response = port_49154_response
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=600))
await hass.async_block_till_done()
assert mock_init.mock_calls[0][1][0] == "test_integration"
assert mock_init.mock_calls[0][2]["context"] == {
"source": config_entries.SOURCE_SSDP
}
assert (
mock_init.mock_calls[0][2]["data"][ssdp.ATTR_SSDP_LOCATION]
== port_49154_response["location"]
)

View File

@ -1038,6 +1038,7 @@ async def test_trigger_entity(hass):
"unique_id": "via_list-id", "unique_id": "via_list-id",
"device_class": "battery", "device_class": "battery",
"unit_of_measurement": "%", "unit_of_measurement": "%",
"availability": "{{ True }}",
"state": "{{ trigger.event.data.beer + 1 }}", "state": "{{ trigger.event.data.beer + 1 }}",
"picture": "{{ '/local/dogs.png' }}", "picture": "{{ '/local/dogs.png' }}",
"icon": "{{ 'mdi:pirate' }}", "icon": "{{ 'mdi:pirate' }}",
@ -1197,3 +1198,44 @@ async def test_config_top_level(hass):
assert state.state == "5" assert state.state == "5"
assert state.attributes["device_class"] == "battery" assert state.attributes["device_class"] == "battery"
assert state.attributes["state_class"] == "measurement" assert state.attributes["state_class"] == "measurement"
async def test_trigger_entity_available(hass):
"""Test trigger entity availability works."""
assert await async_setup_component(
hass,
"template",
{
"template": [
{
"trigger": {"platform": "event", "event_type": "test_event"},
"sensor": [
{
"name": "Maybe Available",
"availability": "{{ trigger and trigger.event.data.beer == 2 }}",
"state": "{{ trigger.event.data.beer }}",
},
],
},
],
},
)
await hass.async_block_till_done()
# Sensors are unknown if never triggered
state = hass.states.get("sensor.maybe_available")
assert state is not None
assert state.state == STATE_UNKNOWN
hass.bus.async_fire("test_event", {"beer": 2})
await hass.async_block_till_done()
state = hass.states.get("sensor.maybe_available")
assert state.state == "2"
hass.bus.async_fire("test_event", {"beer": 1})
await hass.async_block_till_done()
state = hass.states.get("sensor.maybe_available")
assert state.state == "unavailable"

View File

@ -52,55 +52,6 @@ def mock_venv():
yield yield
@pytest.mark.skipif(
not sys.platform.startswith("linux"),
reason="Only works on linux",
)
async def test_discovered_by_observer_before_started(hass, operating_system):
"""Test a device is discovered by the observer before started."""
async def _mock_monitor_observer_callback(callback):
await hass.async_add_executor_job(
callback, MagicMock(action="add", device_path="/dev/new")
)
def _create_mock_monitor_observer(monitor, callback, name):
hass.async_create_task(_mock_monitor_observer_callback(callback))
return MagicMock()
new_usb = [{"domain": "test1", "vid": "3039", "pid": "3039"}]
mock_comports = [
MagicMock(
device=slae_sh_device.device,
vid=12345,
pid=12345,
serial_number=slae_sh_device.serial_number,
manufacturer=slae_sh_device.manufacturer,
description=slae_sh_device.description,
)
]
with patch(
"homeassistant.components.usb.async_get_usb", return_value=new_usb
), patch(
"homeassistant.components.usb.comports", return_value=mock_comports
), patch(
"pyudev.MonitorObserver", new=_create_mock_monitor_observer
):
assert await async_setup_component(hass, "usb", {"usb": {}})
await hass.async_block_till_done()
with patch("homeassistant.components.usb.comports", return_value=[]), patch.object(
hass.config_entries.flow, "async_init"
) as mock_config_flow:
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert len(mock_config_flow.mock_calls) == 1
assert mock_config_flow.mock_calls[0][1][0] == "test1"
@pytest.mark.skipif( @pytest.mark.skipif(
not sys.platform.startswith("linux"), not sys.platform.startswith("linux"),
reason="Only works on linux", reason="Only works on linux",

View File

@ -756,10 +756,7 @@ async def test_usb_discovery_already_running(hass, supervisor, addon_running):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"discovery_info", "discovery_info",
[ [CP2652_ZIGBEE_DISCOVERY_INFO],
NORTEK_ZIGBEE_DISCOVERY_INFO,
CP2652_ZIGBEE_DISCOVERY_INFO,
],
) )
async def test_abort_usb_discovery_aborts_specific_devices( async def test_abort_usb_discovery_aborts_specific_devices(
hass, supervisor, addon_options, discovery_info hass, supervisor, addon_options, discovery_info

View File

@ -27,14 +27,23 @@ async def apply_stop_hass(stop_hass):
"""Make sure all hass are stopped.""" """Make sure all hass are stopped."""
@pytest.fixture
def mock_is_file():
"""Mock is_file."""
# All files exist except for the old entity registry file
with patch(
"os.path.isfile", lambda path: not path.endswith("entity_registry.yaml")
):
yield
def normalize_yaml_files(check_dict): def normalize_yaml_files(check_dict):
"""Remove configuration path from ['yaml_files'].""" """Remove configuration path from ['yaml_files']."""
root = get_test_config_dir() root = get_test_config_dir()
return [key.replace(root, "...") for key in sorted(check_dict["yaml_files"].keys())] return [key.replace(root, "...") for key in sorted(check_dict["yaml_files"].keys())]
@patch("os.path.isfile", return_value=True) def test_bad_core_config(mock_is_file, loop):
def test_bad_core_config(isfile_patch, loop):
"""Test a bad core config setup.""" """Test a bad core config setup."""
files = {YAML_CONFIG_FILE: BAD_CORE_CONFIG} files = {YAML_CONFIG_FILE: BAD_CORE_CONFIG}
with patch_yaml_files(files): with patch_yaml_files(files):
@ -43,8 +52,7 @@ def test_bad_core_config(isfile_patch, loop):
assert res["except"]["homeassistant"][1] == {"unit_system": "bad"} assert res["except"]["homeassistant"][1] == {"unit_system": "bad"}
@patch("os.path.isfile", return_value=True) def test_config_platform_valid(mock_is_file, loop):
def test_config_platform_valid(isfile_patch, loop):
"""Test a valid platform setup.""" """Test a valid platform setup."""
files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: demo"} files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: demo"}
with patch_yaml_files(files): with patch_yaml_files(files):
@ -57,8 +65,7 @@ def test_config_platform_valid(isfile_patch, loop):
assert len(res["yaml_files"]) == 1 assert len(res["yaml_files"]) == 1
@patch("os.path.isfile", return_value=True) def test_component_platform_not_found(mock_is_file, loop):
def test_component_platform_not_found(isfile_patch, loop):
"""Test errors if component or platform not found.""" """Test errors if component or platform not found."""
# Make sure they don't exist # Make sure they don't exist
files = {YAML_CONFIG_FILE: BASE_CONFIG + "beer:"} files = {YAML_CONFIG_FILE: BASE_CONFIG + "beer:"}
@ -89,8 +96,7 @@ def test_component_platform_not_found(isfile_patch, loop):
assert len(res["yaml_files"]) == 1 assert len(res["yaml_files"]) == 1
@patch("os.path.isfile", return_value=True) def test_secrets(mock_is_file, loop):
def test_secrets(isfile_patch, loop):
"""Test secrets config checking method.""" """Test secrets config checking method."""
secrets_path = get_test_config_dir("secrets.yaml") secrets_path = get_test_config_dir("secrets.yaml")
@ -121,8 +127,7 @@ def test_secrets(isfile_patch, loop):
] ]
@patch("os.path.isfile", return_value=True) def test_package_invalid(mock_is_file, loop):
def test_package_invalid(isfile_patch, loop):
"""Test an invalid package.""" """Test an invalid package."""
files = { files = {
YAML_CONFIG_FILE: BASE_CONFIG + (" packages:\n p1:\n" ' group: ["a"]') YAML_CONFIG_FILE: BASE_CONFIG + (" packages:\n p1:\n" ' group: ["a"]')

View File

@ -56,11 +56,14 @@ async def test_home_assistant_core_config_validation(hass):
assert result is None assert result is None
async def test_async_enable_logging(hass): async def test_async_enable_logging(hass, caplog):
"""Test to ensure logging is migrated to the queue handlers.""" """Test to ensure logging is migrated to the queue handlers."""
with patch("logging.getLogger"), patch( with patch("logging.getLogger"), patch(
"homeassistant.bootstrap.async_activate_log_queue_handler" "homeassistant.bootstrap.async_activate_log_queue_handler"
) as mock_async_activate_log_queue_handler: ) as mock_async_activate_log_queue_handler, patch(
"homeassistant.bootstrap.logging.handlers.RotatingFileHandler.doRollover",
side_effect=OSError,
):
bootstrap.async_enable_logging(hass) bootstrap.async_enable_logging(hass)
mock_async_activate_log_queue_handler.assert_called_once() mock_async_activate_log_queue_handler.assert_called_once()
mock_async_activate_log_queue_handler.reset_mock() mock_async_activate_log_queue_handler.reset_mock()
@ -75,6 +78,8 @@ async def test_async_enable_logging(hass):
for f in glob.glob("testing_config/home-assistant.log*"): for f in glob.glob("testing_config/home-assistant.log*"):
os.remove(f) os.remove(f)
assert "Error rolling over log file" in caplog.text
async def test_load_hassio(hass): async def test_load_hassio(hass):
"""Test that we load Hass.io component.""" """Test that we load Hass.io component."""