| # Copyright 2022 The Pigweed Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| # use this file except in compliance with the License. You may obtain a copy of |
| # the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations under |
| # the License. |
| """Tests for pw_ide.editors""" |
| |
| from collections import OrderedDict |
| from enum import Enum |
| import unittest |
| |
| from pw_ide.editors import ( |
| dict_deep_merge, |
| dict_swap_type, |
| EditorSettingsFile, |
| EditorSettingsManager, |
| JsonFileFormat, |
| Json5FileFormat, |
| YamlFileFormat, |
| _StructuredFileFormat, |
| ) |
| |
| from test_cases import PwIdeTestCase |
| |
| |
| class TestDictDeepMerge(unittest.TestCase): |
| """Tests dict_deep_merge""" |
| |
| # pylint: disable=unnecessary-lambda |
| def test_invariants_with_dict_success(self): |
| dict_a = {'hello': 'world'} |
| dict_b = {'foo': 'bar'} |
| |
| expected = { |
| 'hello': 'world', |
| 'foo': 'bar', |
| } |
| |
| result = dict_deep_merge(dict_b, dict_a, lambda: dict()) |
| self.assertEqual(result, expected) |
| |
| def test_invariants_with_dict_implicit_ctor_success(self): |
| dict_a = {'hello': 'world'} |
| dict_b = {'foo': 'bar'} |
| |
| expected = { |
| 'hello': 'world', |
| 'foo': 'bar', |
| } |
| |
| result = dict_deep_merge(dict_b, dict_a) |
| self.assertEqual(result, expected) |
| |
| def test_invariants_with_dict_fails_wrong_ctor_type(self): |
| dict_a = {'hello': 'world'} |
| dict_b = {'foo': 'bar'} |
| |
| with self.assertRaises(TypeError): |
| dict_deep_merge(dict_b, dict_a, lambda: OrderedDict()) |
| |
| def test_invariants_with_ordered_dict_success(self): |
| dict_a = OrderedDict({'hello': 'world'}) |
| dict_b = OrderedDict({'foo': 'bar'}) |
| |
| expected = OrderedDict( |
| { |
| 'hello': 'world', |
| 'foo': 'bar', |
| } |
| ) |
| |
| result = dict_deep_merge(dict_b, dict_a, lambda: OrderedDict()) |
| self.assertEqual(result, expected) |
| |
| def test_invariants_with_ordered_dict_implicit_ctor_success(self): |
| dict_a = OrderedDict({'hello': 'world'}) |
| dict_b = OrderedDict({'foo': 'bar'}) |
| |
| expected = OrderedDict( |
| { |
| 'hello': 'world', |
| 'foo': 'bar', |
| } |
| ) |
| |
| result = dict_deep_merge(dict_b, dict_a) |
| self.assertEqual(result, expected) |
| |
| def test_invariants_with_ordered_dict_fails_wrong_ctor_type(self): |
| dict_a = OrderedDict({'hello': 'world'}) |
| dict_b = OrderedDict({'foo': 'bar'}) |
| |
| with self.assertRaises(TypeError): |
| dict_deep_merge(dict_b, dict_a, lambda: dict()) |
| |
| # pylint: enable=unnecessary-lambda |
| |
| def test_merge_basic(self): |
| dict_a = {'hello': 'world'} |
| dict_b = {'hello': 'bar'} |
| |
| expected = {'hello': 'bar'} |
| result = dict_deep_merge(dict_b, dict_a) |
| self.assertEqual(result, expected) |
| |
| def test_merge_nested_dict(self): |
| dict_a = {'hello': {'a': 'foo'}} |
| dict_b = {'hello': {'b': 'bar'}} |
| |
| expected = {'hello': {'a': 'foo', 'b': 'bar'}} |
| result = dict_deep_merge(dict_b, dict_a) |
| self.assertEqual(result, expected) |
| |
| def test_merge_list(self): |
| dict_a = {'hello': ['world']} |
| dict_b = {'hello': ['bar']} |
| |
| expected = {'hello': ['world', 'bar']} |
| result = dict_deep_merge(dict_b, dict_a) |
| self.assertEqual(result, expected) |
| |
| def test_merge_list_no_duplicates(self): |
| dict_a = {'hello': ['world']} |
| dict_b = {'hello': ['world']} |
| |
| expected = {'hello': ['world']} |
| result = dict_deep_merge(dict_b, dict_a) |
| self.assertEqual(result, expected) |
| |
| def test_merge_nested_dict_with_lists(self): |
| dict_a = {'hello': {'a': 'foo', 'c': ['lorem']}} |
| dict_b = {'hello': {'b': 'bar', 'c': ['ipsum']}} |
| |
| expected = {'hello': {'a': 'foo', 'b': 'bar', 'c': ['lorem', 'ipsum']}} |
| result = dict_deep_merge(dict_b, dict_a) |
| self.assertEqual(result, expected) |
| |
| def test_merge_object_fails(self): |
| class Strawman: |
| pass |
| |
| dict_a = {'hello': 'world'} |
| dict_b = {'foo': Strawman()} |
| |
| with self.assertRaises(TypeError): |
| dict_deep_merge(dict_b, dict_a) |
| |
| def test_merge_copies_string(self): |
| test_str = 'bar' |
| dict_a = {'hello': {'a': 'foo'}} |
| dict_b = {'hello': {'b': test_str}} |
| |
| result = dict_deep_merge(dict_b, dict_a) |
| test_str = 'something else' |
| |
| self.assertEqual(result['hello']['b'], 'bar') |
| |
| |
| class TestDictSwapType(unittest.TestCase): |
| """Tests dict_swap_type""" |
| |
| def test_ordereddict_to_dict(self): |
| """Test converting an OrderedDict to a plain dict""" |
| |
| ordered_dict = OrderedDict( |
| { |
| 'hello': 'world', |
| 'foo': 'bar', |
| 'nested': OrderedDict( |
| { |
| 'lorem': 'ipsum', |
| 'dolor': 'sit amet', |
| } |
| ), |
| } |
| ) |
| |
| plain_dict = dict_swap_type(ordered_dict, dict) |
| |
| expected_plain_dict = { |
| 'hello': 'world', |
| 'foo': 'bar', |
| 'nested': { |
| 'lorem': 'ipsum', |
| 'dolor': 'sit amet', |
| }, |
| } |
| |
| # The returned dict has the content and type we expect |
| self.assertDictEqual(plain_dict, expected_plain_dict) |
| self.assertIsInstance(plain_dict, dict) |
| self.assertIsInstance(plain_dict['nested'], dict) |
| |
| # The original OrderedDict is unchanged |
| self.assertIsInstance(ordered_dict, OrderedDict) |
| self.assertIsInstance(ordered_dict['nested'], OrderedDict) |
| |
| |
| class EditorSettingsTestType(Enum): |
| SETTINGS = 'settings' |
| |
| |
| class TestCasesGenericOnFileFormat: |
| """Container for tests generic on FileFormat. |
| |
| This misdirection is needed to prevent the base test class cases from being |
| run as actual tests. |
| """ |
| |
| class EditorSettingsFileTestCase(PwIdeTestCase): |
| """Test case for EditorSettingsFile with a provided FileFormat""" |
| |
| def setUp(self): |
| if not hasattr(self, 'file_format'): |
| self.file_format = _StructuredFileFormat() |
| return super().setUp() |
| |
| def test_open_new_file_and_write(self): |
| name = 'settings' |
| settings_file = EditorSettingsFile( |
| self.temp_dir_path, name, self.file_format |
| ) |
| |
| with settings_file.build() as settings: |
| settings['hello'] = 'world' |
| |
| with open( |
| self.temp_dir_path / f'{name}.{self.file_format.ext}' |
| ) as file: |
| settings_dict = self.file_format.load(file) |
| |
| self.assertEqual(settings_dict['hello'], 'world') |
| |
| def test_open_new_file_and_get(self): |
| name = 'settings' |
| settings_file = EditorSettingsFile( |
| self.temp_dir_path, name, self.file_format |
| ) |
| |
| with settings_file.build() as settings: |
| settings['hello'] = 'world' |
| |
| settings_dict = settings_file.get() |
| self.assertEqual(settings_dict['hello'], 'world') |
| |
| class EditorSettingsManagerTestCase(PwIdeTestCase): |
| """Test case for EditorSettingsManager with a provided FileFormat""" |
| |
| def setUp(self): |
| if not hasattr(self, 'file_format'): |
| self.file_format = _StructuredFileFormat() |
| return super().setUp() |
| |
| def test_settings_merge(self): |
| """Test that settings merge as expected in isolation.""" |
| default_settings = OrderedDict( |
| { |
| 'foo': 'bar', |
| 'baz': 'qux', |
| 'lorem': OrderedDict( |
| { |
| 'ipsum': 'dolor', |
| } |
| ), |
| } |
| ) |
| |
| types_with_defaults = { |
| EditorSettingsTestType.SETTINGS: lambda _: default_settings |
| } |
| |
| ide_settings = self.make_ide_settings() |
| manager = EditorSettingsManager( |
| ide_settings, |
| self.temp_dir_path, |
| self.file_format, |
| types_with_defaults, |
| ) |
| |
| project_settings = OrderedDict( |
| { |
| 'alpha': 'beta', |
| 'baz': 'xuq', |
| 'foo': 'rab', |
| } |
| ) |
| |
| with manager.project( |
| EditorSettingsTestType.SETTINGS |
| ).build() as settings: |
| dict_deep_merge(project_settings, settings) |
| |
| user_settings = OrderedDict( |
| { |
| 'baz': 'xqu', |
| 'lorem': OrderedDict( |
| { |
| 'ipsum': 'sit amet', |
| 'consectetur': 'adipiscing', |
| } |
| ), |
| } |
| ) |
| |
| with manager.user( |
| EditorSettingsTestType.SETTINGS |
| ).build() as settings: |
| dict_deep_merge(user_settings, settings) |
| |
| expected = { |
| 'alpha': 'beta', |
| 'foo': 'rab', |
| 'baz': 'xqu', |
| 'lorem': { |
| 'ipsum': 'sit amet', |
| 'consectetur': 'adipiscing', |
| }, |
| } |
| |
| with manager.active( |
| EditorSettingsTestType.SETTINGS |
| ).build() as active_settings: |
| manager.default(EditorSettingsTestType.SETTINGS).sync_to( |
| active_settings |
| ) |
| manager.project(EditorSettingsTestType.SETTINGS).sync_to( |
| active_settings |
| ) |
| manager.user(EditorSettingsTestType.SETTINGS).sync_to( |
| active_settings |
| ) |
| |
| self.assertCountEqual( |
| manager.active(EditorSettingsTestType.SETTINGS).get(), expected |
| ) |
| |
| |
| class TestEditorSettingsFileJsonFormat( |
| TestCasesGenericOnFileFormat.EditorSettingsFileTestCase |
| ): |
| """Test EditorSettingsFile with JsonFormat""" |
| |
| def setUp(self): |
| self.file_format = JsonFileFormat() |
| return super().setUp() |
| |
| |
| class TestEditorSettingsManagerJsonFormat( |
| TestCasesGenericOnFileFormat.EditorSettingsManagerTestCase |
| ): |
| """Test EditorSettingsManager with JsonFormat""" |
| |
| def setUp(self): |
| self.file_format = JsonFileFormat() |
| return super().setUp() |
| |
| |
| class TestEditorSettingsFileJson5Format( |
| TestCasesGenericOnFileFormat.EditorSettingsFileTestCase |
| ): |
| """Test EditorSettingsFile with Json5Format""" |
| |
| def setUp(self): |
| self.file_format = Json5FileFormat() |
| return super().setUp() |
| |
| |
| class TestEditorSettingsManagerJson5Format( |
| TestCasesGenericOnFileFormat.EditorSettingsManagerTestCase |
| ): |
| """Test EditorSettingsManager with Json5Format""" |
| |
| def setUp(self): |
| self.file_format = Json5FileFormat() |
| return super().setUp() |
| |
| |
| class TestEditorSettingsFileYamlFormat( |
| TestCasesGenericOnFileFormat.EditorSettingsFileTestCase |
| ): |
| """Test EditorSettingsFile with YamlFormat""" |
| |
| def setUp(self): |
| self.file_format = YamlFileFormat() |
| return super().setUp() |
| |
| |
| class TestEditorSettingsManagerYamlFormat( |
| TestCasesGenericOnFileFormat.EditorSettingsManagerTestCase |
| ): |
| """Test EditorSettingsManager with YamlFormat""" |
| |
| def setUp(self): |
| self.file_format = YamlFileFormat() |
| return super().setUp() |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |