# 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()