async def test_parallel_updates_sync_platform_with_constant(hass): """Test sync platform can set parallel_updates limit.""" platform = MockPlatform() platform.PARALLEL_UPDATES = 2 loader.set_component(hass, 'test_domain.platform', platform) component = EntityComponent(_LOGGER, DOMAIN, hass) component._platforms = {} await component.async_setup({ DOMAIN: { 'platform': 'platform', } }) handle = list(component._platforms.values())[-1] assert handle.parallel_updates == 2 class SyncEntity(MockEntity): """Mock entity that has update.""" async def update(self): pass entity = SyncEntity() await handle.async_add_entities([entity]) assert entity.parallel_updates is not None assert entity.parallel_updates._value == 2
def test_setup(self): # Bogus config self.assertFalse(switch.setup(self.hass, {})) self.assertFalse(switch.setup(self.hass, {switch.DOMAIN: {}})) # Test with non-existing component self.assertFalse(switch.setup( self.hass, {switch.DOMAIN: {CONF_PLATFORM: 'nonexisting'}} )) # Test if switch component returns 0 switches test_platform = loader.get_component('switch.test') test_platform.init(True) self.assertEqual( [], test_platform.get_switches(None, None)) self.assertFalse(switch.setup( self.hass, {switch.DOMAIN: {CONF_PLATFORM: 'test'}} )) # Test if we can load 2 platforms loader.set_component('switch.test2', test_platform) test_platform.init(False) self.assertTrue(switch.setup( self.hass, { switch.DOMAIN: {CONF_PLATFORM: 'test'}, '{} 2'.format(switch.DOMAIN): {CONF_PLATFORM: 'test2'}, } ))
async def test_parallel_updates_sync_platform(hass): """Test sync platform parallel_updates default set to 1.""" platform = MockPlatform() loader.set_component(hass, 'test_domain.platform', platform) component = EntityComponent(_LOGGER, DOMAIN, hass) component._platforms = {} await component.async_setup({ DOMAIN: { 'platform': 'platform', } }) handle = list(component._platforms.values())[-1] assert handle.parallel_updates is None class SyncEntity(MockEntity): """Mock entity that has update.""" async def update(self): pass entity = SyncEntity() await handle.async_add_entities([entity]) assert entity.parallel_updates is not None assert entity.parallel_updates._value == 1
def test_create_account(hass, client): """Test a flow that creates an account.""" set_component( hass, 'test', MockModule('test', async_setup_entry=mock_coro_func(True))) class TestFlow(core_ce.ConfigFlow): VERSION = 1 @asyncio.coroutine def async_step_user(self, user_input=None): return self.async_create_entry( title='Test Entry', data={'secret': 'account_token'} ) with patch.dict(HANDLERS, {'test': TestFlow}): resp = yield from client.post('/api/config/config_entries/flow', json={'handler': 'test'}) assert resp.status == 200 data = yield from resp.json() data.pop('flow_id') assert data == { 'handler': 'test', 'title': 'Test Entry', 'type': 'create_entry', 'version': 1, 'description': None, 'description_placeholders': None, }
def test_remove_entry_raises(manager): """Test if a component raises while removing entry.""" @asyncio.coroutine def mock_unload_entry(hass, entry): """Mock unload entry function.""" raise Exception("BROKEN") loader.set_component( 'test', MockModule('comp', async_unload_entry=mock_unload_entry)) MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager) MockConfigEntry(domain='test', entry_id='test2').add_to_manager(manager) MockConfigEntry(domain='test', entry_id='test3').add_to_manager(manager) assert [item.entry_id for item in manager.async_entries()] == \ ['test1', 'test2', 'test3'] result = yield from manager.async_remove('test2') assert result == { 'require_restart': True } assert [item.entry_id for item in manager.async_entries()] == \ ['test1', 'test3']
def test_component_platform_not_found(self): """Test errors if component or platform not found.""" # Make sure they don't exist set_component('beer', None) set_component('light.beer', None) files = { 'badcomponent.yaml': BASE_CONFIG + 'beer:', 'badplatform.yaml': BASE_CONFIG + 'light:\n platform: beer', } with patch_yaml_files(files): res = check_config.check(get_test_config_dir('badcomponent.yaml')) change_yaml_files(res) self.assertDictEqual({}, res['components']) self.assertDictEqual({ check_config.ERROR_STR: [ 'Component not found: beer', 'Setup failed for beer: Component not found.'] }, res['except']) self.assertDictEqual({}, res['secret_cache']) self.assertDictEqual({}, res['secrets']) self.assertListEqual(['.../badcomponent.yaml'], res['yaml_files']) res = check_config.check(get_test_config_dir('badplatform.yaml')) change_yaml_files(res) assert res['components'] == {'light': [], 'group': None} assert res['except'] == { check_config.ERROR_STR: [ 'Platform not found: light.beer', ]} self.assertDictEqual({}, res['secret_cache']) self.assertDictEqual({}, res['secrets']) self.assertListEqual(['.../badplatform.yaml'], res['yaml_files'])
def test_validate_platform_config_4(self): """Test entity_namespace in PLATFORM_SCHEMA.""" component_schema = PLATFORM_SCHEMA_BASE platform_schema = PLATFORM_SCHEMA loader.set_component( self.hass, 'platform_conf', MockModule('platform_conf', platform_schema_base=component_schema)) loader.set_component( self.hass, 'platform_conf.whatever', MockPlatform('whatever', platform_schema=platform_schema)) with assert_setup_component(1): assert setup.setup_component(self.hass, 'platform_conf', { 'platform_conf': { # pass: entity_namespace accepted by PLATFORM_SCHEMA 'platform': 'whatever', 'entity_namespace': 'yummy', } }) self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf')
def test_platform_specific_config_validation(self): """Test platform that specifies config.""" platform_schema = PLATFORM_SCHEMA.extend({ 'valid': True, }, extra=vol.PREVENT_EXTRA) loader.set_component( 'switch.platform_a', MockPlatform('comp_b', platform_schema=platform_schema)) assert not bootstrap.setup_component(self.hass, 'switch', { 'switch': { 'platform': 'platform_a', 'invalid': True } }) assert not bootstrap.setup_component(self.hass, 'switch', { 'switch': { 'platform': 'platform_a', 'valid': True, 'invalid_extra': True, } }) assert bootstrap.setup_component(self.hass, 'switch', { 'switch': { 'platform': 'platform_a', 'valid': True } })
def test_component_not_installed_if_requirement_fails(self, mock_install): """Component setup should fail if requirement can't install.""" loader.set_component( 'comp', MockModule('comp', requirements=['package==0.0.1'])) assert not bootstrap.setup_component(self.hass, 'comp') assert 'comp' not in self.hass.config.components
async def test_discovery_notification(hass): """Test that we create/dismiss a notification when source is discovery.""" loader.set_component(hass, 'test', MockModule('test')) await async_setup_component(hass, 'persistent_notification', {}) class TestFlow(config_entries.ConfigFlow): VERSION = 5 async def async_step_discovery(self, user_input=None): if user_input is not None: return self.async_create_entry( title='Test Title', data={ 'token': 'abcd' } ) return self.async_show_form( step_id='discovery', ) with patch.dict(config_entries.HANDLERS, {'test': TestFlow}): result = await hass.config_entries.flow.async_init( 'test', context={'source': config_entries.SOURCE_DISCOVERY}) await hass.async_block_till_done() state = hass.states.get('persistent_notification.config_entry_discovery') assert state is not None result = await hass.config_entries.flow.async_configure( result['flow_id'], {}) assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY await hass.async_block_till_done() state = hass.states.get('persistent_notification.config_entry_discovery') assert state is None
async def test_entry_reload_error(hass, manager, state): """Test that we can reload an entry.""" entry = MockConfigEntry( domain='comp', state=state ) entry.add_to_hass(hass) async_setup = MagicMock(return_value=mock_coro(True)) async_setup_entry = MagicMock(return_value=mock_coro(True)) async_unload_entry = MagicMock(return_value=mock_coro(True)) loader.set_component(hass, 'comp', MockModule( 'comp', async_setup=async_setup, async_setup_entry=async_setup_entry, async_unload_entry=async_unload_entry )) with pytest.raises(config_entries.OperationNotAllowed): assert await manager.async_reload(entry.entry_id) assert len(async_unload_entry.mock_calls) == 0 assert len(async_setup.mock_calls) == 0 assert len(async_setup_entry.mock_calls) == 0 assert entry.state == state
async def test_remove_entry_handles_callback_error(hass, manager): """Test that exceptions in the remove callback are handled.""" mock_setup_entry = MagicMock(return_value=mock_coro(True)) mock_unload_entry = MagicMock(return_value=mock_coro(True)) mock_remove_entry = MagicMock( side_effect=lambda *args, **kwargs: mock_coro()) loader.set_component(hass, 'test', MockModule( 'test', async_setup_entry=mock_setup_entry, async_unload_entry=mock_unload_entry, async_remove_entry=mock_remove_entry )) entry = MockConfigEntry( domain='test', entry_id='test1', ) entry.add_to_manager(manager) # Check all config entries exist assert [item.entry_id for item in manager.async_entries()] == \ ['test1'] # Setup entry await entry.async_setup(hass) await hass.async_block_till_done() # Remove entry result = await manager.async_remove('test1') await hass.async_block_till_done() # Check that unload went well and so no need to restart assert result == { 'require_restart': False } # Check the remove callback was invoked. assert mock_remove_entry.call_count == 1 # Check that config entry was removed. assert [item.entry_id for item in manager.async_entries()] == []
def test_component_failing_setup(self): """Test component that fails setup.""" loader.set_component( 'comp', MockModule('comp', setup=lambda hass, config: False)) assert not bootstrap._setup_component(self.hass, 'comp', None) assert 'comp' not in self.hass.config.components
async def test_parallel_updates_async_platform(hass): """Test async platform does not have parallel_updates limit by default.""" platform = MockPlatform() loader.set_component(hass, 'test_domain.platform', platform) component = EntityComponent(_LOGGER, DOMAIN, hass) component._platforms = {} await component.async_setup({ DOMAIN: { 'platform': 'platform', } }) handle = list(component._platforms.values())[-1] assert handle.parallel_updates is None class AsyncEntity(MockEntity): """Mock entity that has async_update.""" async def async_update(self): pass entity = AsyncEntity() await handle.async_add_entities([entity]) assert entity.parallel_updates is None
def test_component_platform_not_found(self, isfile_patch): """Test errors if component or platform not found.""" # Make sure they don't exist set_component('beer', None) files = { YAML_CONFIG_FILE: BASE_CONFIG + 'beer:', } with patch_yaml_files(files): res = check_config.check(get_test_config_dir()) assert res['components'].keys() == {'homeassistant'} assert res['except'] == { check_config.ERROR_STR: ['Component not found: beer']} assert res['secret_cache'] == {} assert res['secrets'] == {} assert len(res['yaml_files']) == 1 set_component('light.beer', None) files = { YAML_CONFIG_FILE: BASE_CONFIG + 'light:\n platform: beer', } with patch_yaml_files(files): res = check_config.check(get_test_config_dir()) assert res['components'].keys() == {'homeassistant', 'light'} assert res['components']['light'] == [] assert res['except'] == { check_config.ERROR_STR: [ 'Platform not found: light.beer', ]} assert res['secret_cache'] == {} assert res['secrets'] == {} assert len(res['yaml_files']) == 1
def test_setup_recovers_when_setup_raises(self): """Test the setup if exceptions are happening.""" platform1_setup = Mock(side_effect=Exception("Broken")) platform2_setup = Mock(return_value=None) loader.set_component("test_domain.mod1", MockPlatform(platform1_setup)) loader.set_component("test_domain.mod2", MockPlatform(platform2_setup)) component = EntityComponent(_LOGGER, DOMAIN, self.hass) assert not platform1_setup.called assert not platform2_setup.called component.setup( OrderedDict( [ (DOMAIN, {"platform": "mod1"}), ("{} 2".format(DOMAIN), {"platform": "non_exist"}), ("{} 3".format(DOMAIN), {"platform": "mod2"}), ] ) ) assert platform1_setup.called assert platform2_setup.called
def test_set_entity_namespace_via_config(self): """Test setting an entity namespace.""" def platform_setup(hass, config, add_devices, discovery_info=None): """Test the platform setup.""" add_devices([ MockEntity(name='beer'), MockEntity(name=None), ]) platform = MockPlatform(platform_setup) loader.set_component(self.hass, 'test_domain.platform', platform) component = EntityComponent(_LOGGER, DOMAIN, self.hass) component.setup({ DOMAIN: { 'platform': 'platform', 'entity_namespace': 'yummy' } }) self.hass.block_till_done() assert sorted(self.hass.states.entity_ids()) == \ ['test_domain.yummy_beer', 'test_domain.yummy_unnamed_device']
def test_component_dependencies(hass): """Test if we can get the proper load order of components.""" loader.set_component(hass, 'mod1', MockModule('mod1')) loader.set_component(hass, 'mod2', MockModule('mod2', ['mod1'])) loader.set_component(hass, 'mod3', MockModule('mod3', ['mod2'])) assert {'mod1', 'mod2', 'mod3'} == \ loader.component_dependencies(hass, 'mod3') # Create circular dependency loader.set_component(hass, 'mod1', MockModule('mod1', ['mod3'])) with pytest.raises(loader.CircularDependency): print(loader.component_dependencies(hass, 'mod3')) # Depend on non-existing component loader.set_component(hass, 'mod1', MockModule('mod1', ['nonexisting'])) with pytest.raises(loader.ComponentNotFound): print(loader.component_dependencies(hass, 'mod1')) # Try to get dependencies for non-existing component with pytest.raises(loader.ComponentNotFound): print(loader.component_dependencies(hass, 'nonexisting'))
def test_pararell_updates_async_platform_with_constant(hass): """Warn we log when platform setup takes a long time.""" platform = MockPlatform() @asyncio.coroutine def mock_update(*args, **kwargs): pass platform.async_setup_platform = mock_update platform.PARALLEL_UPDATES = 1 loader.set_component('test_domain.platform', platform) component = EntityComponent(_LOGGER, DOMAIN, hass) component._platforms = {} yield from component.async_setup({ DOMAIN: { 'platform': 'platform', } }) handle = list(component._platforms.values())[-1] assert handle.parallel_updates is not None
def test_add_entry_calls_setup_entry(hass, manager): """Test we call setup_config_entry.""" mock_setup_entry = MagicMock(return_value=mock_coro(True)) loader.set_component( 'comp', MockModule('comp', async_setup_entry=mock_setup_entry)) class TestFlow(data_entry_flow.FlowHandler): VERSION = 1 @asyncio.coroutine def async_step_init(self, user_input=None): return self.async_create_entry( title='title', data={ 'token': 'supersecret' }) with patch.dict(config_entries.HANDLERS, {'comp': TestFlow, 'beer': 5}): yield from manager.flow.async_init('comp') yield from hass.async_block_till_done() assert len(mock_setup_entry.mock_calls) == 1 p_hass, p_entry = mock_setup_entry.mock_calls[0][1] assert p_hass is hass assert p_entry.data == { 'token': 'supersecret' }
def test_validate_component_config(self): """Test validating component configuration.""" config_schema = vol.Schema({ 'comp_conf': { 'hello': str } }, required=True) loader.set_component( 'comp_conf', MockModule('comp_conf', config_schema=config_schema)) assert not bootstrap._setup_component(self.hass, 'comp_conf', {}) assert not bootstrap._setup_component(self.hass, 'comp_conf', { 'comp_conf': None }) assert not bootstrap._setup_component(self.hass, 'comp_conf', { 'comp_conf': {} }) assert not bootstrap._setup_component(self.hass, 'comp_conf', { 'comp_conf': { 'hello': 'world', 'invalid': 'extra', } }) assert bootstrap._setup_component(self.hass, 'comp_conf', { 'comp_conf': { 'hello': 'world', } })
async def test_saving_and_loading(hass): """Test that we're saving and loading correctly.""" loader.set_component( hass, 'test', MockModule('test', async_setup_entry=lambda *args: mock_coro(True))) class TestFlow(config_entries.ConfigFlow): VERSION = 5 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL @asyncio.coroutine def async_step_user(self, user_input=None): return self.async_create_entry( title='Test Title', data={ 'token': 'abcd' } ) with patch.dict(config_entries.HANDLERS, {'test': TestFlow}): await hass.config_entries.flow.async_init( 'test', context={'source': config_entries.SOURCE_USER}) class Test2Flow(config_entries.ConfigFlow): VERSION = 3 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH @asyncio.coroutine def async_step_user(self, user_input=None): return self.async_create_entry( title='Test 2 Title', data={ 'username': '******' } ) with patch('homeassistant.config_entries.HANDLERS.get', return_value=Test2Flow): await hass.config_entries.flow.async_init( 'test', context={'source': config_entries.SOURCE_USER}) # To trigger the call_later async_fire_time_changed(hass, dt.utcnow() + timedelta(seconds=1)) # To execute the save await hass.async_block_till_done() # Now load written data in new config manager manager = config_entries.ConfigEntries(hass, {}) await manager.async_initialize() # Ensure same order for orig, loaded in zip(hass.config_entries.async_entries(), manager.async_entries()): assert orig.version == loaded.version assert orig.domain == loaded.domain assert orig.title == loaded.title assert orig.data == loaded.data assert orig.source == loaded.source assert orig.connection_class == loaded.connection_class
def test_two_step_flow(hass, client): """Test we can finish a two step flow.""" set_component( hass, 'test', MockModule('test', async_setup_entry=mock_coro_func(True))) class TestFlow(core_ce.ConfigFlow): VERSION = 1 @asyncio.coroutine def async_step_user(self, user_input=None): return self.async_show_form( step_id='account', data_schema=vol.Schema({ 'user_title': str })) @asyncio.coroutine def async_step_account(self, user_input=None): return self.async_create_entry( title=user_input['user_title'], data={'secret': 'account_token'} ) with patch.dict(HANDLERS, {'test': TestFlow}): resp = yield from client.post('/api/config/config_entries/flow', json={'handler': 'test'}) assert resp.status == 200 data = yield from resp.json() flow_id = data.pop('flow_id') assert data == { 'type': 'form', 'handler': 'test', 'step_id': 'account', 'data_schema': [ { 'name': 'user_title', 'type': 'string' } ], 'description_placeholders': None, 'errors': None } with patch.dict(HANDLERS, {'test': TestFlow}): resp = yield from client.post( '/api/config/config_entries/flow/{}'.format(flow_id), json={'user_title': 'user-title'}) assert resp.status == 200 data = yield from resp.json() data.pop('flow_id') assert data == { 'handler': 'test', 'type': 'create_entry', 'title': 'user-title', 'version': 1, 'description': None, 'description_placeholders': None, }
def test_validate_platform_config(self): """Test validating platform configuration.""" platform_schema = PLATFORM_SCHEMA.extend({ 'hello': str, }) loader.set_component( 'platform_conf', MockModule('platform_conf', platform_schema=platform_schema)) loader.set_component( 'platform_conf.whatever', MockPlatform('whatever')) assert not bootstrap._setup_component(self.hass, 'platform_conf', { 'platform_conf': None }) assert not bootstrap._setup_component(self.hass, 'platform_conf', { 'platform_conf': {} }) assert not bootstrap._setup_component(self.hass, 'platform_conf', { 'platform_conf': { 'hello': 'world', 'invalid': 'extra', } }) assert not bootstrap._setup_component(self.hass, 'platform_conf', { 'platform_conf': { 'platform': 'whatever', 'hello': 'world', }, 'platform_conf 2': { 'invalid': True } }) assert not bootstrap._setup_component(self.hass, 'platform_conf', { 'platform_conf': { 'platform': 'not_existing', 'hello': 'world', } }) assert bootstrap._setup_component(self.hass, 'platform_conf', { 'platform_conf': { 'platform': 'whatever', 'hello': 'world', } }) assert bootstrap._setup_component(self.hass, 'platform_conf', { 'platform_conf': [{ 'platform': 'whatever', 'hello': 'world', }] })
def test_component_cannot_depend_config(hass): """Test config is not allowed to be a dependency.""" loader.set_component( 'test_component1', MockModule('test_component1', dependencies=['config'])) with pytest.raises(HomeAssistantError): yield from bootstrap.async_from_config_dict( {'test_component1': None}, hass)
def test_platform_cannot_depend_config(): """Test config is not allowed to be a dependency.""" loader.set_component( 'test_component1.test', MockPlatform('whatever', dependencies=['config'])) with pytest.raises(HomeAssistantError): yield from bootstrap.async_prepare_setup_platform( mock.MagicMock(), {}, 'test_component1', 'test')
def test_circular_import(self): """Test we don't break doing circular import. This test will have test_component discover the switch.test_circular component while setting up. The supplied config will load test_component and will load switch.test_circular. That means that after startup, we will have test_component and switch setup. The test_circular platform has been loaded twice. """ component_calls = [] platform_calls = [] def component_setup(hass, config): """Setup mock component.""" discovery.load_platform(hass, 'switch', 'test_circular', 'disc', config) component_calls.append(1) return True def setup_platform(hass, config, add_devices_callback, discovery_info=None): """Setup mock platform.""" platform_calls.append('disc' if discovery_info else 'component') loader.set_component( 'test_component', MockModule('test_component', setup=component_setup)) loader.set_component( 'switch.test_circular', MockPlatform(setup_platform, dependencies=['test_component'])) bootstrap.setup_component(self.hass, 'test_component', { 'test_component': None, 'switch': [{ 'platform': 'test_circular', }], }) # We wait for the setup_lock to finish run_coroutine_threadsafe( self.hass.data['setup_lock'].acquire(), self.hass.loop).result() self.hass.block_till_done() # test_component will only be setup once assert len(component_calls) == 1 # The platform will be setup once via the config in `setup_component` # and once via the discovery inside test_component. assert len(platform_calls) == 2 assert 'test_component' in self.hass.config.components assert 'switch' in self.hass.config.components
def test_component_exception_setup(self): """Test component that raises exception during setup.""" def exception_setup(hass, config): """Setup that raises exception.""" raise Exception('fail!') loader.set_component('comp', MockModule('comp', setup=exception_setup)) assert not bootstrap._setup_component(self.hass, 'comp', None) assert 'comp' not in self.hass.config.components
def test_platform_no_warn_slow(hass): """Do not warn for long entity setup time.""" loader.set_component( 'test_component1', MockModule('test_component1', platform_schema=PLATFORM_SCHEMA)) with mock.patch.object(hass.loop, 'call_later', mock.MagicMock()) \ as mock_call: result = yield from setup.async_setup_component( hass, 'test_component1', {}) assert result assert not mock_call.called
def test_component_not_setup_missing_dependencies(self): """Test we do not setup a component if not all dependencies loaded.""" deps = ['non_existing'] loader.set_component('comp', MockModule('comp', dependencies=deps)) assert not bootstrap._setup_component(self.hass, 'comp', None) assert 'comp' not in self.hass.config.components self.hass.config.components.append('non_existing') assert bootstrap._setup_component(self.hass, 'comp', None)
def test_all_work_done_before_start(self): """Test all init work done till start.""" call_order = [] def component1_setup(hass, config): """Setup mock component.""" discovery.discover(hass, 'test_component2', component='test_component2') discovery.discover(hass, 'test_component3', component='test_component3') return True def component_track_setup(hass, config): """Setup mock component.""" call_order.append(1) return True loader.set_component( 'test_component1', MockModule('test_component1', setup=component1_setup)) loader.set_component( 'test_component2', MockModule('test_component2', setup=component_track_setup)) loader.set_component( 'test_component3', MockModule('test_component3', setup=component_track_setup)) @callback def track_start(event): """Track start event.""" call_order.append(2) self.hass.bus.listen_once(EVENT_HOMEASSISTANT_START, track_start) self.hass.add_job( setup.async_setup_component(self.hass, 'test_component1', {})) self.hass.block_till_done() self.hass.start() assert call_order == [1, 1, 2]
def test_setup_dependencies_platform(hass): """Test we setup the dependencies of a platform. We're explictely testing that we process dependencies even if a component with the same name has already been loaded. """ loader.set_component('test_component', MockModule('test_component')) loader.set_component('test_component2', MockModule('test_component2')) loader.set_component( 'test_domain.test_component', MockPlatform(dependencies=['test_component', 'test_component2'])) component = EntityComponent(_LOGGER, DOMAIN, hass) yield from async_setup_component(hass, 'test_component', {}) yield from component.async_setup( {DOMAIN: { 'platform': 'test_component', }}) assert 'test_component' in hass.config.components assert 'test_component2' in hass.config.components assert 'test_domain.test_component' in hass.config.components
def _async_setup_component(hass: core.HomeAssistant, domain: str, config) -> bool: """Set up a component for Home Assistant. This method is a coroutine. """ def log_error(msg, link=True): """Log helper.""" _LOGGER.error("Setup failed for %s: %s", domain, msg) async_notify_setup_error(hass, domain, link) component = loader.get_component(domain) if not component: log_error("Component not found.", False) return False # Validate no circular dependencies components = loader.load_order_component(domain) # OrderedSet is empty if component or dependencies could not be resolved if not components: log_error("Unable to resolve component or dependencies.") return False processed_config = \ conf_util.async_process_component_config(hass, config, domain) if processed_config is None: log_error("Invalid config.") return False if not hass.config.skip_pip and hasattr(component, 'REQUIREMENTS'): req_success = yield from _async_process_requirements( hass, domain, component.REQUIREMENTS) if not req_success: log_error("Could not install all requirements.") return False if hasattr(component, 'DEPENDENCIES'): dep_success = yield from _async_process_dependencies( hass, config, domain, component.DEPENDENCIES) if not dep_success: log_error("Could not setup all dependencies.") return False async_comp = hasattr(component, 'async_setup') _LOGGER.info("Setting up %s", domain) warn_task = hass.loop.call_later(SLOW_SETUP_WARNING, _LOGGER.warning, "Setup of %s is taking over %s seconds.", domain, SLOW_SETUP_WARNING) try: if async_comp: result = yield from component.async_setup(hass, processed_config) else: result = yield from hass.loop.run_in_executor( None, component.setup, hass, processed_config) except Exception: # pylint: disable=broad-except _LOGGER.exception("Error during setup of component %s", domain) async_notify_setup_error(hass, domain, True) return False finally: warn_task.cancel() if result is False: log_error("Component failed to initialize.") return False elif result is not True: log_error("Component did not return boolean if setup was successful. " "Disabling component.") loader.set_component(domain, None) return False hass.config.components.add(component.DOMAIN) # Cleanup if domain in hass.data[DATA_SETUP]: hass.data[DATA_SETUP].pop(domain) hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}) return True
def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool: """Setup a component for Home Assistant.""" # pylint: disable=too-many-return-statements,too-many-branches # pylint: disable=too-many-statements if domain in hass.config.components: return True with _SETUP_LOCK: # It might have been loaded while waiting for lock if domain in hass.config.components: return True if domain in _CURRENT_SETUP: _LOGGER.error('Attempt made to setup %s during setup of %s', domain, domain) return False component = loader.get_component(domain) missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', []) if dep not in hass.config.components] if missing_deps: _LOGGER.error( 'Not initializing %s because not all dependencies loaded: %s', domain, ", ".join(missing_deps)) return False if hasattr(component, 'CONFIG_SCHEMA'): try: config = component.CONFIG_SCHEMA(config) except vol.MultipleInvalid as ex: _log_exception(ex, domain, config) return False elif hasattr(component, 'PLATFORM_SCHEMA'): platforms = [] for p_name, p_config in config_per_platform(config, domain): # Validate component specific platform schema try: p_validated = component.PLATFORM_SCHEMA(p_config) except vol.MultipleInvalid as ex: _log_exception(ex, domain, p_config) return False # Not all platform components follow same pattern for platforms # So if p_name is None we are not going to validate platform # (the automation component is one of them) if p_name is None: platforms.append(p_validated) continue platform = prepare_setup_platform(hass, config, domain, p_name) if platform is None: return False # Validate platform specific schema if hasattr(platform, 'PLATFORM_SCHEMA'): try: p_validated = platform.PLATFORM_SCHEMA(p_validated) except vol.MultipleInvalid as ex: _log_exception(ex, '{}.{}'.format(domain, p_name), p_validated) return False platforms.append(p_validated) # Create a copy of the configuration with all config for current # component removed and add validated config back in. filter_keys = extract_domain_configs(config, domain) config = {key: value for key, value in config.items() if key not in filter_keys} config[domain] = platforms if not _handle_requirements(hass, component, domain): return False _CURRENT_SETUP.append(domain) try: result = component.setup(hass, config) if result is False: _LOGGER.error('component %s failed to initialize', domain) return False elif result is not True: _LOGGER.error('component %s did not return boolean if setup ' 'was successful. Disabling component.', domain) loader.set_component(domain, None) return False except Exception: # pylint: disable=broad-except _LOGGER.exception('Error during setup of component %s', domain) return False finally: _CURRENT_SETUP.remove(domain) hass.config.components.append(component.DOMAIN) # Assumption: if a component does not depend on groups # it communicates with devices if group.DOMAIN not in getattr(component, 'DEPENDENCIES', []): hass.pool.add_worker() hass.bus.fire( EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}) return True
def test_set_component(hass): """Test if set_component works.""" comp = object() loader.set_component(hass, 'switch.test_set', comp) assert loader.get_component(hass, 'switch.test_set') is comp
async def test_remove_entry(hass, manager): """Test that we can remove an entry.""" async def mock_setup_entry(hass, entry): """Mock setting up entry.""" hass.loop.create_task( hass.config_entries.async_forward_entry_setup(entry, 'light')) return True async def mock_unload_entry(hass, entry): """Mock unloading an entry.""" result = await hass.config_entries.async_forward_entry_unload( entry, 'light') assert result return result entity = MockEntity( unique_id='1234', name='Test Entity', ) async def mock_setup_entry_platform(hass, entry, async_add_entities): """Mock setting up platform.""" async_add_entities([entity]) loader.set_component( hass, 'test', MockModule('test', async_setup_entry=mock_setup_entry, async_unload_entry=mock_unload_entry)) loader.set_component( hass, 'light.test', MockPlatform(async_setup_entry=mock_setup_entry_platform)) MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager) entry = MockConfigEntry( domain='test', entry_id='test2', ) entry.add_to_manager(manager) MockConfigEntry(domain='test', entry_id='test3').add_to_manager(manager) # Check all config entries exist assert [item.entry_id for item in manager.async_entries()] == \ ['test1', 'test2', 'test3'] # Setup entry await entry.async_setup(hass) await hass.async_block_till_done() # Check entity state got added assert hass.states.get('light.test_entity') is not None # Group all_lights, light.test_entity assert len(hass.states.async_all()) == 2 # Check entity got added to entity registry ent_reg = await hass.helpers.entity_registry.async_get_registry() assert len(ent_reg.entities) == 1 entity_entry = list(ent_reg.entities.values())[0] assert entity_entry.config_entry_id == entry.entry_id # Remove entry result = await manager.async_remove('test2') await hass.async_block_till_done() # Check that unload went well and so no need to restart assert result == {'require_restart': False} # Check that config entry was removed. assert [item.entry_id for item in manager.async_entries()] == \ ['test1', 'test3'] # Check that entity state has been removed assert hass.states.get('light.test_entity') is None # Just Group all_lights assert len(hass.states.async_all()) == 1 # Check that entity registry entry no longer references config_entry_id entity_entry = list(ent_reg.entities.values())[0] assert entity_entry.config_entry_id is None
def test_set_component(self): """ Test if set_component works. """ loader.set_component('switch.test', mock_toggledevice_platform) self.assertEqual( mock_toggledevice_platform, loader.get_component('switch.test'))
def test_set_component(self): """Test if set_component works.""" loader.set_component('switch.test_set', http) self.assertEqual(http, loader.get_component('switch.test_set'))
def setUp(self): # pylint: disable=invalid-name self.hass = get_test_home_assistant() loader.prepare(self.hass) loader.set_component('light.test', mock_toggledevice_platform)
def _async_setup_component(hass: core.HomeAssistant, domain: str, config) -> bool: """Setup a component for Home Assistant. This method is a coroutine. """ # pylint: disable=too-many-return-statements if domain in hass.config.components: return True setup_lock = hass.data.get('setup_lock') if setup_lock is None: setup_lock = hass.data['setup_lock'] = asyncio.Lock(loop=hass.loop) setup_progress = hass.data.get('setup_progress') if setup_progress is None: setup_progress = hass.data['setup_progress'] = [] if domain in setup_progress: _LOGGER.error('Attempt made to setup %s during setup of %s', domain, domain) _async_persistent_notification(hass, domain, True) return False try: # Used to indicate to discovery that a setup is ongoing and allow it # to wait till it is done. did_lock = False if not setup_lock.locked(): yield from setup_lock.acquire() did_lock = True setup_progress.append(domain) config = yield from async_prepare_setup_component(hass, config, domain) if config is None: return False component = loader.get_component(domain) if component is None: _async_persistent_notification(hass, domain) return False async_comp = hasattr(component, 'async_setup') try: _LOGGER.info("Setting up %s", domain) if async_comp: result = yield from component.async_setup(hass, config) else: result = yield from hass.loop.run_in_executor( None, component.setup, hass, config) except Exception: # pylint: disable=broad-except _LOGGER.exception('Error during setup of component %s', domain) _async_persistent_notification(hass, domain, True) return False if result is False: _LOGGER.error('component %s failed to initialize', domain) _async_persistent_notification(hass, domain, True) return False elif result is not True: _LOGGER.error( 'component %s did not return boolean if setup ' 'was successful. Disabling component.', domain) _async_persistent_notification(hass, domain, True) loader.set_component(domain, None) return False hass.config.components.append(component.DOMAIN) hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}) return True finally: setup_progress.remove(domain) if did_lock: setup_lock.release()
async def test_two_step_options_flow(hass, client): """Test we can finish a two step options flow.""" set_component(hass, 'test', MockModule('test', async_setup_entry=mock_coro_func(True))) class TestFlow(core_ce.ConfigFlow): @staticmethod @callback def async_get_options_flow(config, options): class OptionsFlowHandler(data_entry_flow.FlowHandler): def __init__(self, config, options): self.config = config self.options = options async def async_step_init(self, user_input=None): return self.async_show_form(step_id='finish', data_schema=vol.Schema( {'enabled': bool})) async def async_step_finish(self, user_input=None): return self.async_create_entry(title='Enable disable', data=user_input) return OptionsFlowHandler(config, options) MockConfigEntry( domain='test', entry_id='test1', source='bla', connection_class=core_ce.CONN_CLASS_LOCAL_POLL, ).add_to_hass(hass) entry = hass.config_entries._entries[0] with patch.dict(HANDLERS, {'test': TestFlow}): url = '/api/config/config_entries/entry/option/flow' resp = await client.post(url, json={'handler': entry.entry_id}) assert resp.status == 200 data = await resp.json() flow_id = data.pop('flow_id') assert data == { 'type': 'form', 'handler': 'test1', 'step_id': 'finish', 'data_schema': [{ 'name': 'enabled', 'type': 'boolean' }], 'description_placeholders': None, 'errors': None } with patch.dict(HANDLERS, {'test': TestFlow}): resp = await client.post( '/api/config/config_entries/options/flow/{}'.format(flow_id), json={'enabled': True}) assert resp.status == 200 data = await resp.json() data.pop('flow_id') assert data == { 'handler': 'test1', 'type': 'create_entry', 'title': 'Enable disable', 'version': 1, 'description': None, 'description_placeholders': None, }
def test_load_order_components(self): loader.set_component('mod1', MockModule('mod1', ['group'])) loader.set_component('mod2', MockModule('mod2', ['mod1', 'sun'])) loader.set_component('mod3', MockModule('mod3', ['mod2'])) loader.set_component('mod4', MockModule('mod4', ['group'])) self.assertEqual(['group', 'mod4', 'mod1', 'sun', 'mod2', 'mod3'], loader.load_order_components(['mod4', 'mod3', 'mod2'])) loader.set_component('mod1', MockModule('mod1')) loader.set_component('mod2', MockModule('mod2', ['group'])) self.assertEqual(['mod1', 'group', 'mod2'], loader.load_order_components(['mod2', 'mod1'])) # Add a non existing one self.assertEqual(['mod1', 'group', 'mod2'], loader.load_order_components( ['mod2', 'nonexisting', 'mod1'])) # Depend on a non existing one loader.set_component('mod1', MockModule('mod1', ['nonexisting'])) self.assertEqual(['group', 'mod2'], loader.load_order_components(['mod2', 'mod1']))
def mock_test_component(hass): """Ensure a component called 'test' exists.""" set_component(hass, 'test', MockModule('test'))
def test_saving_and_loading(hass): """Test that we're saving and loading correctly.""" loader.set_component(hass, 'test', MockModule('test')) class TestFlow(data_entry_flow.FlowHandler): VERSION = 5 @asyncio.coroutine def async_step_init(self, user_input=None): return self.async_create_entry( title='Test Title', data={ 'token': 'abcd' } ) with patch.dict(config_entries.HANDLERS, {'test': TestFlow}): yield from hass.config_entries.flow.async_init('test') class Test2Flow(data_entry_flow.FlowHandler): VERSION = 3 @asyncio.coroutine def async_step_init(self, user_input=None): return self.async_create_entry( title='Test 2 Title', data={ 'username': '******' } ) json_path = 'homeassistant.util.json.open' with patch('homeassistant.config_entries.HANDLERS.get', return_value=Test2Flow), \ patch.object(config_entries, 'SAVE_DELAY', 0): yield from hass.config_entries.flow.async_init('test') with patch(json_path, mock_open(), create=True) as mock_write: # To trigger the call_later yield from asyncio.sleep(0, loop=hass.loop) # To execute the save yield from hass.async_block_till_done() # Mock open calls are: open file, context enter, write, context leave written = mock_write.mock_calls[2][1][0] # Now load written data in new config manager manager = config_entries.ConfigEntries(hass, {}) with patch('os.path.isfile', return_value=True), \ patch(json_path, mock_open(read_data=written), create=True): yield from manager.async_load() # Ensure same order for orig, loaded in zip(hass.config_entries.async_entries(), manager.async_entries()): assert orig.version == loaded.version assert orig.domain == loaded.domain assert orig.title == loaded.title assert orig.data == loaded.data assert orig.source == loaded.source
def test_validate_platform_config(self): """Test validating platform configuration.""" platform_schema = PLATFORM_SCHEMA.extend({ 'hello': str, }) loader.set_component( 'platform_conf', MockModule('platform_conf', platform_schema=platform_schema)) loader.set_component('platform_conf.whatever', MockPlatform('whatever')) with assert_setup_component(0): assert bootstrap._setup_component( self.hass, 'platform_conf', {'platform_conf': { 'hello': 'world', 'invalid': 'extra', }}) self.hass.config.components.remove('platform_conf') with assert_setup_component(1): assert bootstrap._setup_component( self.hass, 'platform_conf', { 'platform_conf': { 'platform': 'whatever', 'hello': 'world', }, 'platform_conf 2': { 'invalid': True } }) self.hass.config.components.remove('platform_conf') with assert_setup_component(0): assert bootstrap._setup_component(self.hass, 'platform_conf', { 'platform_conf': { 'platform': 'not_existing', 'hello': 'world', } }) self.hass.config.components.remove('platform_conf') with assert_setup_component(1): assert bootstrap._setup_component(self.hass, 'platform_conf', { 'platform_conf': { 'platform': 'whatever', 'hello': 'world', } }) self.hass.config.components.remove('platform_conf') with assert_setup_component(1): assert bootstrap._setup_component(self.hass, 'platform_conf', { 'platform_conf': [{ 'platform': 'whatever', 'hello': 'world', }] }) self.hass.config.components.remove('platform_conf') # Any falsey platform config will be ignored (None, {}, etc) with assert_setup_component(0) as config: assert bootstrap._setup_component(self.hass, 'platform_conf', {'platform_conf': None}) assert 'platform_conf' in self.hass.config.components assert not config['platform_conf'] # empty assert bootstrap._setup_component(self.hass, 'platform_conf', {'platform_conf': {}}) assert 'platform_conf' in self.hass.config.components assert not config['platform_conf'] # empty
async def _async_setup_component(hass: core.HomeAssistant, domain: str, config) -> bool: """Set up a component for Home Assistant. This method is a coroutine. """ def log_error(msg, link=True): """Log helper.""" _LOGGER.error("Setup failed for %s: %s", domain, msg) async_notify_setup_error(hass, domain, link) component = loader.get_component(hass, domain) if not component: log_error("Component not found.", False) return False # Validate no circular dependencies components = loader.load_order_component(hass, domain) # OrderedSet is empty if component or dependencies could not be resolved if not components: log_error("Unable to resolve component or dependencies.") return False processed_config = \ conf_util.async_process_component_config(hass, config, domain) if processed_config is None: log_error("Invalid config.") return False try: await async_process_deps_reqs(hass, config, domain, component) except HomeAssistantError as err: log_error(str(err)) return False start = timer() _LOGGER.info("Setting up %s", domain) if hasattr(component, 'PLATFORM_SCHEMA'): # Entity components have their own warning warn_task = None else: warn_task = hass.loop.call_later( SLOW_SETUP_WARNING, _LOGGER.warning, "Setup of %s is taking over %s seconds.", domain, SLOW_SETUP_WARNING) try: if hasattr(component, 'async_setup'): result = await component.async_setup( # type: ignore hass, processed_config) else: result = await hass.async_add_job(component.setup, hass, processed_config) # type: ignore except Exception: # pylint: disable=broad-except _LOGGER.exception("Error during setup of component %s", domain) async_notify_setup_error(hass, domain, True) return False finally: end = timer() if warn_task: warn_task.cancel() _LOGGER.info("Setup of domain %s took %.1f seconds.", domain, end - start) if result is False: log_error("Component failed to initialize.") return False elif result is not True: log_error("Component did not return boolean if setup was successful. " "Disabling component.") loader.set_component(hass, domain, None) return False for entry in hass.config_entries.async_entries(domain): await entry.async_setup(hass, component=component) hass.config.components.add(component.DOMAIN) # type: ignore # Cleanup if domain in hass.data[DATA_SETUP]: hass.data[DATA_SETUP].pop(domain) hass.bus.async_fire( EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN} # type: ignore ) return True
def test_validate_platform_config(self, caplog): """Test validating platform configuration.""" platform_schema = PLATFORM_SCHEMA.extend({ 'hello': str, }) platform_schema_base = PLATFORM_SCHEMA_BASE.extend({}) loader.set_component( self.hass, 'platform_conf', MockModule('platform_conf', platform_schema_base=platform_schema_base)) loader.set_component( self.hass, 'platform_conf.whatever', MockPlatform('whatever', platform_schema=platform_schema)) with assert_setup_component(1): assert setup.setup_component( self.hass, 'platform_conf', { 'platform_conf': { 'platform': 'whatever', 'hello': 'world', 'invalid': 'extra', } }) assert caplog.text.count('Your configuration contains ' 'extra keys') == 1 self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') with assert_setup_component(2): assert setup.setup_component( self.hass, 'platform_conf', { 'platform_conf': { 'platform': 'whatever', 'hello': 'world', }, 'platform_conf 2': { 'platform': 'whatever', 'invalid': True } }) assert caplog.text.count('Your configuration contains ' 'extra keys') == 2 self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') with assert_setup_component(0): assert setup.setup_component(self.hass, 'platform_conf', { 'platform_conf': { 'platform': 'not_existing', 'hello': 'world', } }) self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') with assert_setup_component(1): assert setup.setup_component(self.hass, 'platform_conf', { 'platform_conf': { 'platform': 'whatever', 'hello': 'world', } }) self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') with assert_setup_component(1): assert setup.setup_component(self.hass, 'platform_conf', { 'platform_conf': [{ 'platform': 'whatever', 'hello': 'world', }] }) self.hass.data.pop(setup.DATA_SETUP) self.hass.config.components.remove('platform_conf') # Any falsey platform config will be ignored (None, {}, etc) with assert_setup_component(0) as config: assert setup.setup_component(self.hass, 'platform_conf', {'platform_conf': None}) assert 'platform_conf' in self.hass.config.components assert not config['platform_conf'] # empty assert setup.setup_component(self.hass, 'platform_conf', {'platform_conf': {}}) assert 'platform_conf' in self.hass.config.components assert not config['platform_conf'] # empty