From fe5ff3811675acbef579920451d1e741705eca15 Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Wed, 26 Sep 2018 12:59:26 -0600 Subject: [PATCH 01/10] Fixed file corruption bugs in private storage code. --- homeassistant/helpers/storage.py | 4 +++- homeassistant/util/json.py | 5 ++++- tests/helpers/test_storage.py | 9 +++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index 7d867b50ec2ace..9f62df9c410d59 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -187,7 +187,9 @@ def _write_data(self, path: str, data: Dict): os.makedirs(os.path.dirname(path)) _LOGGER.debug('Writing data for %s', self.key) - json.save_json(path, data, self._private) + tmp_path = path + "__TEMP__" + json.save_json(tmp_path, data, self._private) + os.replace(tmp_path, path) async def _async_migrate_func(self, old_version, old_data): """Migrate to the new version.""" diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index d6f7e14904099b..31e2ca873f8b20 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -4,6 +4,7 @@ import json import os +from os import O_WRONLY, O_CREAT, O_TRUNC from homeassistant.exceptions import HomeAssistantError @@ -47,7 +48,9 @@ def save_json(filename: str, data: Union[List, Dict], """ try: json_data = json.dumps(data, sort_keys=True, indent=4) - with open(filename, 'w', encoding='utf-8') as fdesc: + mode = 0o600 if private else 0o644 + with open(os.open(filename, O_WRONLY | O_CREAT | O_TRUNC, mode), + 'w', encoding='utf-8') as fdesc: fdesc.write(json_data) except TypeError as error: _LOGGER.exception('Failed to serialize to JSON: %s', diff --git a/tests/helpers/test_storage.py b/tests/helpers/test_storage.py index 6cb75899d3552a..595092261f81ba 100644 --- a/tests/helpers/test_storage.py +++ b/tests/helpers/test_storage.py @@ -15,6 +15,7 @@ MOCK_VERSION = 1 MOCK_KEY = 'storage-test' MOCK_DATA = {'hello': 'world'} +MOCK_DATA2 = {'goodbye': 'cruel world'} @pytest.fixture @@ -30,6 +31,14 @@ async def test_loading(hass, store): assert data == MOCK_DATA +async def test_overwriting(hass, store): + """Test we can save, overwrite and reload data.""" + await store.async_save(MOCK_DATA) + await store.async_save(MOCK_DATA2) + data = await store.async_load() + assert data == MOCK_DATA2 + + async def test_loading_non_existing(hass, store): """Test we can save and load data.""" with patch('homeassistant.util.json.open', side_effect=FileNotFoundError): From 7df69edb2ba224247b3b309cbf787bb5f9330072 Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Wed, 26 Sep 2018 17:57:26 -0600 Subject: [PATCH 02/10] Restoring fixed test case. --- tests/components/emulated_hue/test_init.py | 97 +++++++++++----------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py index 2f443eb5d6ea78..d416dd08e05a30 100644 --- a/tests/components/emulated_hue/test_init.py +++ b/tests/components/emulated_hue/test_init.py @@ -16,24 +16,25 @@ def test_config_google_home_entity_id_to_number(): handle = mop() with patch('homeassistant.util.json.open', mop, create=True): - number = conf.entity_id_to_number('light.test') - assert number == '2' - assert handle.write.call_count == 1 - assert json.loads(handle.write.mock_calls[0][1][0]) == { - '1': 'light.test2', - '2': 'light.test', - } + with patch('homeassistant.util.json.os.open', return_value=0): + number = conf.entity_id_to_number('light.test') + assert number == '2' + assert handle.write.call_count == 1 + assert json.loads(handle.write.mock_calls[0][1][0]) == { + '1': 'light.test2', + '2': 'light.test', + } - number = conf.entity_id_to_number('light.test') - assert number == '2' - assert handle.write.call_count == 1 + number = conf.entity_id_to_number('light.test') + assert number == '2' + assert handle.write.call_count == 1 - number = conf.entity_id_to_number('light.test2') - assert number == '1' - assert handle.write.call_count == 1 + number = conf.entity_id_to_number('light.test2') + assert number == '1' + assert handle.write.call_count == 1 - entity_id = conf.number_to_entity_id('1') - assert entity_id == 'light.test2' + entity_id = conf.number_to_entity_id('1') + assert entity_id == 'light.test2' def test_config_google_home_entity_id_to_number_altered(): @@ -46,24 +47,25 @@ def test_config_google_home_entity_id_to_number_altered(): handle = mop() with patch('homeassistant.util.json.open', mop, create=True): - number = conf.entity_id_to_number('light.test') - assert number == '22' - assert handle.write.call_count == 1 - assert json.loads(handle.write.mock_calls[0][1][0]) == { - '21': 'light.test2', - '22': 'light.test', - } + with patch('homeassistant.util.json.os.open', return_value=0): + number = conf.entity_id_to_number('light.test') + assert number == '22' + assert handle.write.call_count == 1 + assert json.loads(handle.write.mock_calls[0][1][0]) == { + '21': 'light.test2', + '22': 'light.test', + } - number = conf.entity_id_to_number('light.test') - assert number == '22' - assert handle.write.call_count == 1 + number = conf.entity_id_to_number('light.test') + assert number == '22' + assert handle.write.call_count == 1 - number = conf.entity_id_to_number('light.test2') - assert number == '21' - assert handle.write.call_count == 1 + number = conf.entity_id_to_number('light.test2') + assert number == '21' + assert handle.write.call_count == 1 - entity_id = conf.number_to_entity_id('21') - assert entity_id == 'light.test2' + entity_id = conf.number_to_entity_id('21') + assert entity_id == 'light.test2' def test_config_google_home_entity_id_to_number_empty(): @@ -76,23 +78,24 @@ def test_config_google_home_entity_id_to_number_empty(): handle = mop() with patch('homeassistant.util.json.open', mop, create=True): - number = conf.entity_id_to_number('light.test') - assert number == '1' - assert handle.write.call_count == 1 - assert json.loads(handle.write.mock_calls[0][1][0]) == { - '1': 'light.test', - } - - number = conf.entity_id_to_number('light.test') - assert number == '1' - assert handle.write.call_count == 1 - - number = conf.entity_id_to_number('light.test2') - assert number == '2' - assert handle.write.call_count == 2 - - entity_id = conf.number_to_entity_id('2') - assert entity_id == 'light.test2' + with patch('homeassistant.util.json.os.open', return_value=0): + number = conf.entity_id_to_number('light.test') + assert number == '1' + assert handle.write.call_count == 1 + assert json.loads(handle.write.mock_calls[0][1][0]) == { + '1': 'light.test', + } + + number = conf.entity_id_to_number('light.test') + assert number == '1' + assert handle.write.call_count == 1 + + number = conf.entity_id_to_number('light.test2') + assert number == '2' + assert handle.write.call_count == 2 + + entity_id = conf.number_to_entity_id('2') + assert entity_id == 'light.test2' def test_config_alexa_entity_id_to_number(): From e5cb184bd651a393e96abe0dde381711f8e8bc92 Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Thu, 27 Sep 2018 20:58:37 -0600 Subject: [PATCH 03/10] Implemented test suite for utils/json.py --- tests/helpers/test_storage.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/helpers/test_storage.py b/tests/helpers/test_storage.py index 595092261f81ba..38b8a7cd38039e 100644 --- a/tests/helpers/test_storage.py +++ b/tests/helpers/test_storage.py @@ -31,14 +31,6 @@ async def test_loading(hass, store): assert data == MOCK_DATA -async def test_overwriting(hass, store): - """Test we can save, overwrite and reload data.""" - await store.async_save(MOCK_DATA) - await store.async_save(MOCK_DATA2) - data = await store.async_load() - assert data == MOCK_DATA2 - - async def test_loading_non_existing(hass, store): """Test we can save and load data.""" with patch('homeassistant.util.json.open', side_effect=FileNotFoundError): From 86303e32f132d94cc922c5545b7d0defb2732c39 Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Fri, 28 Sep 2018 06:50:08 -0600 Subject: [PATCH 04/10] Added new unit test cases for util/json.py --- tests/util/test_json.py | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tests/util/test_json.py diff --git a/tests/util/test_json.py b/tests/util/test_json.py new file mode 100644 index 00000000000000..047761eac96771 --- /dev/null +++ b/tests/util/test_json.py @@ -0,0 +1,67 @@ +"""Test Home Assistant json utility functions.""" +import os +import unittest +import sys +from tempfile import mkdtemp + +from homeassistant.util.json import (SerializationError, WriteError, + load_json, save_json) +from homeassistant.exceptions import HomeAssistantError + +# Test data that can be saved as JSON +TEST_JSON_A = {"a":1, "B":"two"} +TEST_JSON_B = {"a":"one", "B":2} +# Test data that can not be saved as JSON (keys must be strings) +TEST_BAD_OBJECT = {("A",):1} +# Test data that can not be loaded as JSON +TEST_BAD_SERIALIED = "THIS IS NOT JSON\n" + +class TestJSON(unittest.TestCase): + """Test util.json save and load""" + + def setUp(self): + self.tmp_dir = mkdtemp() + + def tearDown(self): + for fname in os.listdir(self.tmp_dir): + os.remove(os.path.join(self.tmp_dir, fname)) + os.rmdir(self.tmp_dir) + + def path_for(self, leaf_name): + return os.path.join(self.tmp_dir, leaf_name+".json") + + def test_save_and_load(self): + fname = self.path_for("test1") + save_json(fname, TEST_JSON_A) + data = load_json(fname) + self.assertEqual(data, TEST_JSON_A) + + # Skipped on Windows + @unittest.skipIf(sys.platform.startswith('win'), + "private permissions not supported on Windows") + def test_save_and_load_private(self): + fname = self.path_for("test2") + save_json(fname, TEST_JSON_A, private=True) + data = load_json(fname) + self.assertEqual(data, TEST_JSON_A) + stats = os.stat(fname) + self.assertEqual(stats.st_mode & 0o77, 0) + + def test_overwrite_and_reload(self): + fname = self.path_for("test3") + save_json(fname, TEST_JSON_A) + save_json(fname, TEST_JSON_B) + data = load_json(fname) + self.assertEqual(data, TEST_JSON_B) + + def test_save_bad_data(self): + fname = self.path_for("test4") + with self.assertRaises(SerializationError): + save_json(fname, TEST_BAD_OBJECT) + + def test_load_bad_data(self): + fname = self.path_for("test5") + with open(fname, "w") as fh: + fh.write(TEST_BAD_SERIALIED) + with self.assertRaises(HomeAssistantError): + data = load_json(fname) From 11d03d03bc398c677aab12f1cabb583e5cfe74d5 Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Fri, 28 Sep 2018 06:57:13 -0600 Subject: [PATCH 05/10] Dixed formatting nags --- tests/util/test_json.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/util/test_json.py b/tests/util/test_json.py index 047761eac96771..b906ded64f607a 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -4,18 +4,19 @@ import sys from tempfile import mkdtemp -from homeassistant.util.json import (SerializationError, WriteError, +from homeassistant.util.json import (SerializationError, load_json, save_json) from homeassistant.exceptions import HomeAssistantError # Test data that can be saved as JSON -TEST_JSON_A = {"a":1, "B":"two"} -TEST_JSON_B = {"a":"one", "B":2} +TEST_JSON_A = {"a" : 1, "B" : "two"} +TEST_JSON_B = {"a" : "one", "B" : 2} # Test data that can not be saved as JSON (keys must be strings) -TEST_BAD_OBJECT = {("A",):1} +TEST_BAD_OBJECT = {("A",) : 1} # Test data that can not be loaded as JSON TEST_BAD_SERIALIED = "THIS IS NOT JSON\n" + class TestJSON(unittest.TestCase): """Test util.json save and load""" @@ -26,7 +27,7 @@ def tearDown(self): for fname in os.listdir(self.tmp_dir): os.remove(os.path.join(self.tmp_dir, fname)) os.rmdir(self.tmp_dir) - + def path_for(self, leaf_name): return os.path.join(self.tmp_dir, leaf_name+".json") @@ -53,7 +54,7 @@ def test_overwrite_and_reload(self): save_json(fname, TEST_JSON_B) data = load_json(fname) self.assertEqual(data, TEST_JSON_B) - + def test_save_bad_data(self): fname = self.path_for("test4") with self.assertRaises(SerializationError): @@ -64,4 +65,4 @@ def test_load_bad_data(self): with open(fname, "w") as fh: fh.write(TEST_BAD_SERIALIED) with self.assertRaises(HomeAssistantError): - data = load_json(fname) + load_json(fname) From 10646fafcb46f7523396cb64477d4d850f3a6ec3 Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Fri, 28 Sep 2018 06:59:51 -0600 Subject: [PATCH 06/10] Fixed more nags from the Hound --- tests/util/test_json.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/util/test_json.py b/tests/util/test_json.py index b906ded64f607a..24690c930a114a 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -9,10 +9,10 @@ from homeassistant.exceptions import HomeAssistantError # Test data that can be saved as JSON -TEST_JSON_A = {"a" : 1, "B" : "two"} -TEST_JSON_B = {"a" : "one", "B" : 2} +TEST_JSON_A = {"a": 1, "B": "two"} +TEST_JSON_B = {"a": "one", "B": 2} # Test data that can not be saved as JSON (keys must be strings) -TEST_BAD_OBJECT = {("A",) : 1} +TEST_BAD_OBJECT = {("A",): 1} # Test data that can not be loaded as JSON TEST_BAD_SERIALIED = "THIS IS NOT JSON\n" From 83a191388ba2cdcd21205b6e644a35438deff8fd Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Fri, 28 Sep 2018 07:27:19 -0600 Subject: [PATCH 07/10] Added doc strings to some very short functions --- tests/util/test_json.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/util/test_json.py b/tests/util/test_json.py index 24690c930a114a..e7434a5553acf4 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -18,21 +18,24 @@ class TestJSON(unittest.TestCase): - """Test util.json save and load""" + """Test util.json save and load.""" def setUp(self): + """Setting up for tests.""" self.tmp_dir = mkdtemp() def tearDown(self): + """Cleaning up after tests.""" for fname in os.listdir(self.tmp_dir): os.remove(os.path.join(self.tmp_dir, fname)) os.rmdir(self.tmp_dir) - def path_for(self, leaf_name): + def _path_for(self, leaf_name): return os.path.join(self.tmp_dir, leaf_name+".json") def test_save_and_load(self): - fname = self.path_for("test1") + """Test saving and loading back.""" + fname = self._path_for("test1") save_json(fname, TEST_JSON_A) data = load_json(fname) self.assertEqual(data, TEST_JSON_A) @@ -41,7 +44,8 @@ def test_save_and_load(self): @unittest.skipIf(sys.platform.startswith('win'), "private permissions not supported on Windows") def test_save_and_load_private(self): - fname = self.path_for("test2") + """Test we can load private files and that they are protected.""" + fname = self._path_for("test2") save_json(fname, TEST_JSON_A, private=True) data = load_json(fname) self.assertEqual(data, TEST_JSON_A) @@ -49,19 +53,22 @@ def test_save_and_load_private(self): self.assertEqual(stats.st_mode & 0o77, 0) def test_overwrite_and_reload(self): - fname = self.path_for("test3") + """Test that we can overwrite an existing file and read back.""" + fname = self._path_for("test3") save_json(fname, TEST_JSON_A) save_json(fname, TEST_JSON_B) data = load_json(fname) self.assertEqual(data, TEST_JSON_B) def test_save_bad_data(self): - fname = self.path_for("test4") + """Test error from trying to save unserialisable data.""" + fname = self._path_for("test4") with self.assertRaises(SerializationError): save_json(fname, TEST_BAD_OBJECT) def test_load_bad_data(self): - fname = self.path_for("test5") + """Test error from trying to load unserialisable data.""" + fname = self._path_for("test5") with open(fname, "w") as fh: fh.write(TEST_BAD_SERIALIED) with self.assertRaises(HomeAssistantError): From 070ac97cefa72044f7f72270a07ac551e035d4cb Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Fri, 28 Sep 2018 09:11:14 -0600 Subject: [PATCH 08/10] Fixing lint's complains about my choice of parts of speach. Sigh. --- tests/util/test_json.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/util/test_json.py b/tests/util/test_json.py index e7434a5553acf4..53f62682b5ecc4 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -21,11 +21,11 @@ class TestJSON(unittest.TestCase): """Test util.json save and load.""" def setUp(self): - """Setting up for tests.""" + """Set up for tests.""" self.tmp_dir = mkdtemp() def tearDown(self): - """Cleaning up after tests.""" + """Clean up after tests.""" for fname in os.listdir(self.tmp_dir): os.remove(os.path.join(self.tmp_dir, fname)) os.rmdir(self.tmp_dir) From 52dfcc1694f4dbe1a462ea7c2ce93e111d1fcaf3 Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Mon, 1 Oct 2018 12:39:11 -0600 Subject: [PATCH 09/10] Moved atomic save operations down into util/json.py so that all benefit. Added extra clean-up code to ensure that temporary files are removed in case of errors. Updated emulated_hue unit tests to avoid errors. --- homeassistant/helpers/storage.py | 4 +- homeassistant/util/json.py | 12 ++- tests/components/emulated_hue/test_init.py | 111 +++++++++++---------- 3 files changed, 72 insertions(+), 55 deletions(-) diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index 9f62df9c410d59..7d867b50ec2ace 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -187,9 +187,7 @@ def _write_data(self, path: str, data: Dict): os.makedirs(os.path.dirname(path)) _LOGGER.debug('Writing data for %s', self.key) - tmp_path = path + "__TEMP__" - json.save_json(tmp_path, data, self._private) - os.replace(tmp_path, path) + json.save_json(path, data, self._private) async def _async_migrate_func(self, old_version, old_data): """Migrate to the new version.""" diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index 31e2ca873f8b20..cc6202689aa1c4 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -46,12 +46,14 @@ def save_json(filename: str, data: Union[List, Dict], Returns True on success. """ + tmp_filename = filename + "__TEMP__" try: json_data = json.dumps(data, sort_keys=True, indent=4) mode = 0o600 if private else 0o644 - with open(os.open(filename, O_WRONLY | O_CREAT | O_TRUNC, mode), + with open(os.open(tmp_filename, O_WRONLY | O_CREAT | O_TRUNC, mode), 'w', encoding='utf-8') as fdesc: fdesc.write(json_data) + os.replace(tmp_filename, filename) except TypeError as error: _LOGGER.exception('Failed to serialize to JSON: %s', filename) @@ -60,3 +62,11 @@ def save_json(filename: str, data: Union[List, Dict], _LOGGER.exception('Saving JSON file failed: %s', filename) raise WriteError(error) + finally: + if os.path.exists(tmp_filename): + try: + os.remove(tmp_filename) + except OSError as e: + # If we are cleaning up then something else went wrong, so + # we should suppress likely follow-on errors in the cleanup + _LOGGER.error("JSON file replacement cleanup failed: %s", e) diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py index d416dd08e05a30..9b0a5cd90522bf 100644 --- a/tests/components/emulated_hue/test_init.py +++ b/tests/components/emulated_hue/test_init.py @@ -1,14 +1,16 @@ """Test the Emulated Hue component.""" import json -from unittest.mock import patch, Mock, mock_open +from unittest.mock import patch, Mock, mock_open, MagicMock from homeassistant.components.emulated_hue import Config def test_config_google_home_entity_id_to_number(): """Test config adheres to the type.""" - conf = Config(Mock(), { + mock_hass = Mock() + mock_hass.config.path = MagicMock("path", return_value="test_path") + conf = Config(mock_hass, { 'type': 'google_home' }) @@ -17,29 +19,32 @@ def test_config_google_home_entity_id_to_number(): with patch('homeassistant.util.json.open', mop, create=True): with patch('homeassistant.util.json.os.open', return_value=0): - number = conf.entity_id_to_number('light.test') - assert number == '2' - assert handle.write.call_count == 1 - assert json.loads(handle.write.mock_calls[0][1][0]) == { - '1': 'light.test2', - '2': 'light.test', - } + with patch('homeassistant.util.json.os.replace'): + number = conf.entity_id_to_number('light.test') + assert number == '2' + assert handle.write.call_count == 1 + assert json.loads(handle.write.mock_calls[0][1][0]) == { + '1': 'light.test2', + '2': 'light.test', + } - number = conf.entity_id_to_number('light.test') - assert number == '2' - assert handle.write.call_count == 1 + number = conf.entity_id_to_number('light.test') + assert number == '2' + assert handle.write.call_count == 1 - number = conf.entity_id_to_number('light.test2') - assert number == '1' - assert handle.write.call_count == 1 + number = conf.entity_id_to_number('light.test2') + assert number == '1' + assert handle.write.call_count == 1 - entity_id = conf.number_to_entity_id('1') - assert entity_id == 'light.test2' + entity_id = conf.number_to_entity_id('1') + assert entity_id == 'light.test2' def test_config_google_home_entity_id_to_number_altered(): """Test config adheres to the type.""" - conf = Config(Mock(), { + mock_hass = Mock() + mock_hass.config.path = MagicMock("path", return_value="test_path") + conf = Config(mock_hass, { 'type': 'google_home' }) @@ -48,29 +53,32 @@ def test_config_google_home_entity_id_to_number_altered(): with patch('homeassistant.util.json.open', mop, create=True): with patch('homeassistant.util.json.os.open', return_value=0): - number = conf.entity_id_to_number('light.test') - assert number == '22' - assert handle.write.call_count == 1 - assert json.loads(handle.write.mock_calls[0][1][0]) == { - '21': 'light.test2', - '22': 'light.test', - } + with patch('homeassistant.util.json.os.replace'): + number = conf.entity_id_to_number('light.test') + assert number == '22' + assert handle.write.call_count == 1 + assert json.loads(handle.write.mock_calls[0][1][0]) == { + '21': 'light.test2', + '22': 'light.test', + } - number = conf.entity_id_to_number('light.test') - assert number == '22' - assert handle.write.call_count == 1 + number = conf.entity_id_to_number('light.test') + assert number == '22' + assert handle.write.call_count == 1 - number = conf.entity_id_to_number('light.test2') - assert number == '21' - assert handle.write.call_count == 1 + number = conf.entity_id_to_number('light.test2') + assert number == '21' + assert handle.write.call_count == 1 - entity_id = conf.number_to_entity_id('21') - assert entity_id == 'light.test2' + entity_id = conf.number_to_entity_id('21') + assert entity_id == 'light.test2' def test_config_google_home_entity_id_to_number_empty(): """Test config adheres to the type.""" - conf = Config(Mock(), { + mock_hass = Mock() + mock_hass.config.path = MagicMock("path", return_value="test_path") + conf = Config(mock_hass, { 'type': 'google_home' }) @@ -79,23 +87,24 @@ def test_config_google_home_entity_id_to_number_empty(): with patch('homeassistant.util.json.open', mop, create=True): with patch('homeassistant.util.json.os.open', return_value=0): - number = conf.entity_id_to_number('light.test') - assert number == '1' - assert handle.write.call_count == 1 - assert json.loads(handle.write.mock_calls[0][1][0]) == { - '1': 'light.test', - } - - number = conf.entity_id_to_number('light.test') - assert number == '1' - assert handle.write.call_count == 1 - - number = conf.entity_id_to_number('light.test2') - assert number == '2' - assert handle.write.call_count == 2 - - entity_id = conf.number_to_entity_id('2') - assert entity_id == 'light.test2' + with patch('homeassistant.util.json.os.replace'): + number = conf.entity_id_to_number('light.test') + assert number == '1' + assert handle.write.call_count == 1 + assert json.loads(handle.write.mock_calls[0][1][0]) == { + '1': 'light.test', + } + + number = conf.entity_id_to_number('light.test') + assert number == '1' + assert handle.write.call_count == 1 + + number = conf.entity_id_to_number('light.test2') + assert number == '2' + assert handle.write.call_count == 2 + + entity_id = conf.number_to_entity_id('2') + assert entity_id == 'light.test2' def test_config_alexa_entity_id_to_number(): From f80a92ebcd01a98cbf898a58cda37c4ec5007456 Mon Sep 17 00:00:00 2001 From: Nicko van Someren Date: Mon, 1 Oct 2018 13:46:07 -0600 Subject: [PATCH 10/10] Apparently 'e' is not allows as a variable name for an exception... --- homeassistant/util/json.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index cc6202689aa1c4..0a2a2a1edf3682 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -66,7 +66,7 @@ def save_json(filename: str, data: Union[List, Dict], if os.path.exists(tmp_filename): try: os.remove(tmp_filename) - except OSError as e: + except OSError as err: # If we are cleaning up then something else went wrong, so # we should suppress likely follow-on errors in the cleanup - _LOGGER.error("JSON file replacement cleanup failed: %s", e) + _LOGGER.error("JSON replacement cleanup failed: %s", err)