From d4b6a7343f2675ea5a064bbbc9007f94a2881fbc Mon Sep 17 00:00:00 2001 From: Andrew Thigpen Date: Sun, 27 Dec 2015 13:24:34 -0600 Subject: [PATCH] Fix issue with scene component when using YAML aliases. YAML aliases/anchors can make repetitive configuration sections easier to deal with. However when dealing with dictionaries, care needs to be taken to not modify the original anchor since PyYAML utilizes a reference when encountering an alias instead of a copy of the dictionary. --- homeassistant/components/scene.py | 5 +-- tests/components/test_scene.py | 54 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/scene.py b/homeassistant/components/scene.py index 7c96230ccd4..ce1a3242542 100644 --- a/homeassistant/components/scene.py +++ b/homeassistant/components/scene.py @@ -73,8 +73,9 @@ def _process_config(scene_config): for entity_id in c_entities: if isinstance(c_entities[entity_id], dict): - state = c_entities[entity_id].pop('state', None) - attributes = c_entities[entity_id] + entity_attrs = c_entities[entity_id].copy() + state = entity_attrs.pop('state', None) + attributes = entity_attrs else: state = c_entities[entity_id] attributes = {} diff --git a/tests/components/test_scene.py b/tests/components/test_scene.py index 2fc8fe085c2..0f6663354dd 100644 --- a/tests/components/test_scene.py +++ b/tests/components/test_scene.py @@ -32,6 +32,60 @@ class TestScene(unittest.TestCase): 'scene': [[]] })) + def test_config_yaml_alias_anchor(self): + """ + Tests the usage of YAML aliases and anchors. The following test scene + configuration is equivalent to: + + scene: + - name: test + entities: + light_1: &light_1_state + state: 'on' + brightness: 100 + light_2: *light_1_state + + When encountering a YAML alias/anchor, the PyYAML parser will use a + reference to the original dictionary, instead of creating a copy, so + care needs to be taken to not modify the original. + """ + test_light = loader.get_component('light.test') + test_light.init() + + self.assertTrue(light.setup(self.hass, { + light.DOMAIN: {'platform': 'test'} + })) + + light_1, light_2 = test_light.DEVICES[0:2] + + light.turn_off(self.hass, [light_1.entity_id, light_2.entity_id]) + + self.hass.pool.block_till_done() + + entity_state = { + 'state': 'on', + 'brightness': 100, + } + self.assertTrue(scene.setup(self.hass, { + 'scene': [{ + 'name': 'test', + 'entities': { + light_1.entity_id: entity_state, + light_2.entity_id: entity_state, + } + }] + })) + + scene.activate(self.hass, 'scene.test') + self.hass.pool.block_till_done() + + self.assertTrue(light_1.is_on) + self.assertTrue(light_2.is_on) + self.assertEqual(100, + light_1.last_call('turn_on')[1].get('brightness')) + self.assertEqual(100, + light_2.last_call('turn_on')[1].get('brightness')) + def test_activate_scene(self): test_light = loader.get_component('light.test') test_light.init()