← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~raoul-snyman/openlp/settings-upgrade into lp:openlp

 

Raoul Snyman has proposed merging lp:~raoul-snyman/openlp/settings-upgrade into lp:openlp.

Commit message:
Add multi-to-one settings upgrading, and merge all the post-2.4 settings changes into __setting_upgrade_2__

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~raoul-snyman/openlp/settings-upgrade/+merge/333788

Add multi-to-one settings upgrading, and merge all the post-2.4 settings changes into __setting_upgrade_2__

Add this to your merge proposal:
--------------------------------------------------------------------------------
lp:~raoul-snyman/openlp/settings-upgrade (revision 2791)
https://ci.openlp.io/job/Branch-01-Pull/2295/                          [SUCCESS]
https://ci.openlp.io/job/Branch-02-Functional-Tests/2196/              [SUCCESS]
https://ci.openlp.io/job/Branch-03-Interface-Tests/2074/               [SUCCESS]
https://ci.openlp.io/job/Branch-04a-Code_Analysis/1401/                [SUCCESS]
https://ci.openlp.io/job/Branch-04b-Test_Coverage/1225/                [SUCCESS]
https://ci.openlp.io/job/Branch-04c-Code_Analysis2/355/                [SUCCESS]
https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/187/                 [FAILURE]
Stopping after failure, see https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/187/console for more details

-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~raoul-snyman/openlp/settings-upgrade into lp:openlp.
=== modified file 'nose2.cfg'
--- nose2.cfg	2016-08-10 09:07:49 +0000
+++ nose2.cfg	2017-11-16 05:12:46 +0000
@@ -1,5 +1,5 @@
 [unittest]
-verbose = true
+verbose = True
 plugins = nose2.plugins.mp
 
 [log-capture]
@@ -9,14 +9,19 @@
 log-level = ERROR
 
 [test-result]
-always-on = true
-descriptions = true
+always-on = True
+descriptions = True
 
 [coverage]
-always-on = true
+always-on = True
 coverage = openlp
 coverage-report = html
 
 [multiprocess]
-always-on = false
+always-on = False
 processes = 4
+
+[output-buffer]
+always-on = True
+stderr = True
+stdout = False

=== modified file 'openlp/core/common/settings.py'
--- openlp/core/common/settings.py	2017-11-10 22:50:04 +0000
+++ openlp/core/common/settings.py	2017-11-16 05:12:46 +0000
@@ -40,7 +40,7 @@
 
 # Fix for bug #1014422.
 X11_BYPASS_DEFAULT = True
-if is_linux():
+if is_linux():                                                                              # pragma: no cover
     # Default to False on Gnome.
     X11_BYPASS_DEFAULT = bool(not os.environ.get('GNOME_DESKTOP_SESSION_ID'))
     # Default to False on Xfce.
@@ -206,11 +206,14 @@
         'projector/source dialog type': 0  # Source select dialog box type
     }
     __file_path__ = ''
+    # Settings upgrades prior to 3.0
     __setting_upgrade_1__ = [
-        # Changed during 2.2.x development.
         ('songs/search as type', 'advanced/search as type', []),
         ('media/players', 'media/players_temp', [(media_players_conv, None)]),  # Convert phonon to system
         ('media/players_temp', 'media/players', []),  # Move temp setting from above to correct setting
+    ]
+    # Settings upgrades for 3.0 (aka 2.6)
+    __setting_upgrade_2__ = [
         ('advanced/default color', 'core/logo background color', []),  # Default image renamed + moved to general > 2.4.
         ('advanced/default image', 'core/logo file', []),  # Default image renamed + moved to general after 2.4.
         ('remotes/https enabled', '', []),
@@ -231,9 +234,7 @@
         # Last search type was renamed to last used search type in 2.6 since Bible search value type changed in 2.6.
         ('songs/last search type', 'songs/last used search type', []),
         ('bibles/last search type', '', []),
-        ('custom/last search type', 'custom/last used search type', [])]
-
-    __setting_upgrade_2__ = [
+        ('custom/last search type', 'custom/last used search type', []),
         # The following changes are being made for the conversion to using Path objects made in 2.6 development
         ('advanced/data path', 'advanced/data path', [(str_to_path, None)]),
         ('crashreport/last directory', 'crashreport/last directory', [(str_to_path, None)]),
@@ -467,32 +468,38 @@
         for version in range(current_version, __version__):
             version += 1
             upgrade_list = getattr(self, '__setting_upgrade_{version}__'.format(version=version))
-            for old_key, new_key, rules in upgrade_list:
+            for old_keys, new_key, rules in upgrade_list:
                 # Once removed we don't have to do this again. - Can be removed once fully switched to the versioning
                 # system.
-                if not self.contains(old_key):
+                if not isinstance(old_keys, (tuple, list)):
+                    old_keys = [old_keys]
+                if any([not self.contains(old_key) for old_key in old_keys]):
+                    log.warning('One of {} does not exist, skipping upgrade'.format(old_keys))
                     continue
                 if new_key:
                     # Get the value of the old_key.
-                    old_value = super(Settings, self).value(old_key)
+                    old_values = [super(Settings, self).value(old_key) for old_key in old_keys]
                     # When we want to convert the value, we have to figure out the default value (because we cannot get
                     # the default value from the central settings dict.
                     if rules:
-                        default_value = rules[0][1]
-                        old_value = self._convert_value(old_value, default_value)
+                        default_values = rules[0][1]
+                        if not isinstance(default_values, (list, tuple)):
+                            default_values = [default_values]
+                        old_values = [self._convert_value(old_value, default_value)
+                                      for old_value, default_value in zip(old_values, default_values)]
                     # Iterate over our rules and check what the old_value should be "converted" to.
-                    for new, old in rules:
+                    new_value = None
+                    for new_rule, old_rule in rules:
                         # If the value matches with the condition (rule), then use the provided value. This is used to
                         # convert values. E. g. an old value 1 results in True, and 0 in False.
-                        if callable(new):
-                            old_value = new(old_value)
-                        elif old == old_value:
-                            old_value = new
+                        if callable(new_rule):
+                            new_value = new_rule(*old_values)
+                        elif old_rule in old_values:
+                            new_value = new_rule
                             break
-                    self.setValue(new_key, old_value)
-                if new_key != old_key:
-                    self.remove(old_key)
-        self.setValue('settings/version', version)
+                    self.setValue(new_key, new_value)
+                [self.remove(old_key) for old_key in old_keys if old_key != new_key]
+            self.setValue('settings/version', version)
 
     def value(self, key):
         """

=== modified file 'tests/functional/openlp_core/common/test_settings.py'
--- tests/functional/openlp_core/common/test_settings.py	2017-10-07 07:05:07 +0000
+++ tests/functional/openlp_core/common/test_settings.py	2017-11-16 05:12:46 +0000
@@ -22,10 +22,12 @@
 """
 Package to test the openlp.core.lib.settings package.
 """
+from pathlib import Path
 from unittest import TestCase
-from unittest.mock import patch
+from unittest.mock import call, patch
 
-from openlp.core.common.settings import Settings
+from openlp.core.common import settings
+from openlp.core.common.settings import Settings, media_players_conv
 
 from tests.helpers.testmixin import TestMixin
 
@@ -47,28 +49,58 @@
         """
         self.destroy_settings()
 
-    def test_settings_basic(self):
-        """
-        Test the Settings creation and its default usage
-        """
-        # GIVEN: A new Settings setup
+    def test_media_players_conv(self):
+        """Test the media players conversion function"""
+        # GIVEN: A list of media players
+        media_players = 'phonon,webkit,vlc'
+
+        # WHEN: The media converter function is called
+        result = media_players_conv(media_players)
+
+        # THEN: The list should have been converted correctly
+        assert result == 'system,webkit,vlc'
+
+    def test_default_value(self):
+        """Test reading a setting that doesn't exist yet"""
+        # GIVEN: A setting that doesn't exist yet
 
         # WHEN reading a setting for the first time
         default_value = Settings().value('core/has run wizard')
 
         # THEN the default value is returned
-        self.assertFalse(default_value, 'The default value should be False')
+        assert default_value is False, 'The default value should be False'
 
+    def test_save_new_value(self):
+        """Test saving a new setting"""
+        # GIVEN: A setting that hasn't been saved yet
         # WHEN a new value is saved into config
         Settings().setValue('core/has run wizard', True)
 
         # THEN the new value is returned when re-read
-        self.assertTrue(Settings().value('core/has run wizard'), 'The saved value should have been returned')
+        assert Settings().value('core/has run wizard') is True, 'The saved value should have been returned'
+
+    def test_set_up_default_values(self):
+        """Test that the default values are updated"""
+        # GIVEN: A Settings object with defaults
+        # WHEN: set_up_default_values() is called
+        Settings.set_up_default_values()
+
+        # THEN: The default values should have been added to the dictionary
+        assert 'advanced/default service name' in Settings.__default_settings__
+
+    def test_get_default_value(self):
+        """Test that the default value for a setting is returned"""
+        # GIVEN: A Settings class with a default value
+        Settings.__default_settings__['test/moo'] = 'baa'
+
+        # WHEN: get_default_value() is called
+        result = Settings().get_default_value('test/moo')
+
+        # THEN: The correct default value should be returned
+        assert result == 'baa'
 
     def test_settings_override(self):
-        """
-        Test the Settings creation and its override usage
-        """
+        """Test the Settings creation and its override usage"""
         # GIVEN: an override for the settings
         screen_settings = {
             'test/extend': 'very wide',
@@ -79,18 +111,22 @@
         extend = Settings().value('test/extend')
 
         # THEN the default value is returned
-        self.assertEqual('very wide', extend, 'The default value of "very wide" should be returned')
+        assert extend == 'very wide', 'The default value of "very wide" should be returned'
+
+    def test_save_existing_setting(self):
+        """Test that saving an existing setting returns the new value"""
+        # GIVEN: An existing setting
+        Settings().extend_default_settings({'test/existing value': None})
+        Settings().setValue('test/existing value', 'old value')
 
         # WHEN a new value is saved into config
-        Settings().setValue('test/extend', 'very short')
+        Settings().setValue('test/existing value', 'new value')
 
         # THEN the new value is returned when re-read
-        self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')
+        assert Settings().value('test/existing value') == 'new value', 'The saved value should be returned'
 
     def test_settings_override_with_group(self):
-        """
-        Test the Settings creation and its override usage - with groups
-        """
+        """Test the Settings creation and its override usage - with groups"""
         # GIVEN: an override for the settings
         screen_settings = {
             'test/extend': 'very wide',
@@ -112,9 +148,7 @@
         self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')
 
     def test_settings_nonexisting(self):
-        """
-        Test the Settings on query for non-existing value
-        """
+        """Test the Settings on query for non-existing value"""
         # GIVEN: A new Settings setup
         with self.assertRaises(KeyError) as cm:
             # WHEN reading a setting that doesn't exists
@@ -124,9 +158,7 @@
         self.assertEqual(str(cm.exception), "'core/does not exists'", 'We should get an exception')
 
     def test_extend_default_settings(self):
-        """
-        Test that the extend_default_settings method extends the default settings
-        """
+        """Test that the extend_default_settings method extends the default settings"""
         # GIVEN: A patched __default_settings__ dictionary
         with patch.dict(Settings.__default_settings__,
                         {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 3}, True):
@@ -138,3 +170,125 @@
             self.assertEqual(
                 Settings.__default_settings__, {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 4,
                                                 'test/extended 1': 1, 'test/extended 2': 2})
+
+    @patch('openlp.core.common.settings.QtCore.QSettings.contains')
+    @patch('openlp.core.common.settings.QtCore.QSettings.value')
+    @patch('openlp.core.common.settings.QtCore.QSettings.setValue')
+    @patch('openlp.core.common.settings.QtCore.QSettings.remove')
+    def test_upgrade_single_setting(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains):
+        """Test that the upgrade mechanism for settings works correctly for single value upgrades"""
+        # GIVEN: A settings object with an upgrade step to take (99, so that we don't interfere with real ones)
+        local_settings = Settings()
+        local_settings.__setting_upgrade_99__ = [
+            ('single/value', 'single/new value', [(str, '')])
+        ]
+        settings.__version__ = 99
+        mocked_value.side_effect = [98, 10]
+        mocked_contains.return_value = True
+
+        # WHEN: upgrade_settings() is called
+        local_settings.upgrade_settings()
+
+        # THEN: The correct calls should have been made with the correct values
+        assert mocked_value.call_count == 2, 'Settings().value() should have been called twice'
+        assert mocked_value.call_args_list == [call('settings/version', 0), call('single/value')]
+        assert mocked_setValue.call_count == 2, 'Settings().setValue() should have been called twice'
+        assert mocked_setValue.call_args_list == [call('single/new value', '10'), call('settings/version', 99)]
+        mocked_contains.assert_called_once_with('single/value')
+        mocked_remove.assert_called_once_with('single/value')
+
+    @patch('openlp.core.common.settings.QtCore.QSettings.contains')
+    @patch('openlp.core.common.settings.QtCore.QSettings.value')
+    @patch('openlp.core.common.settings.QtCore.QSettings.setValue')
+    @patch('openlp.core.common.settings.QtCore.QSettings.remove')
+    def test_upgrade_setting_value(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains):
+        """Test that the upgrade mechanism for settings correctly uses the new value when it's not a function"""
+        # GIVEN: A settings object with an upgrade step to take (99, so that we don't interfere with real ones)
+        local_settings = Settings()
+        local_settings.__setting_upgrade_99__ = [
+            ('values/old value', 'values/new value', [(True, 1)])
+        ]
+        settings.__version__ = 99
+        mocked_value.side_effect = [98, 1]
+        mocked_contains.return_value = True
+
+        # WHEN: upgrade_settings() is called
+        local_settings.upgrade_settings()
+
+        # THEN: The correct calls should have been made with the correct values
+        assert mocked_value.call_count == 2, 'Settings().value() should have been called twice'
+        assert mocked_value.call_args_list == [call('settings/version', 0), call('values/old value')]
+        assert mocked_setValue.call_count == 2, 'Settings().setValue() should have been called twice'
+        assert mocked_setValue.call_args_list == [call('values/new value', True), call('settings/version', 99)]
+        mocked_contains.assert_called_once_with('values/old value')
+        mocked_remove.assert_called_once_with('values/old value')
+
+    @patch('openlp.core.common.settings.QtCore.QSettings.contains')
+    @patch('openlp.core.common.settings.QtCore.QSettings.value')
+    @patch('openlp.core.common.settings.QtCore.QSettings.setValue')
+    @patch('openlp.core.common.settings.QtCore.QSettings.remove')
+    def test_upgrade_multiple_one_invalid(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains):
+        """Test that the upgrade mechanism for settings works correctly for multiple values where one is invalid"""
+        # GIVEN: A settings object with an upgrade step to take
+        local_settings = Settings()
+        local_settings.__setting_upgrade_99__ = [
+            (['multiple/value 1', 'multiple/value 2'], 'single/new value', [])
+        ]
+        settings.__version__ = 99
+        mocked_value.side_effect = [98, 10]
+        mocked_contains.side_effect = [True, False]
+
+        # WHEN: upgrade_settings() is called
+        local_settings.upgrade_settings()
+
+        # THEN: The correct calls should have been made with the correct values
+        mocked_value.assert_called_once_with('settings/version', 0)
+        mocked_setValue.assert_called_once_with('settings/version', 99)
+        assert mocked_contains.call_args_list == [call('multiple/value 1'), call('multiple/value 2')]
+
+    def test_can_upgrade(self):
+        """Test the Settings.can_upgrade() method"""
+        # GIVEN: A Settings object
+        local_settings = Settings()
+
+        # WHEN: can_upgrade() is run
+        result = local_settings.can_upgrade()
+
+        # THEN: The result should be True
+        assert result is True, 'The settings should be upgradeable'
+
+    def test_convert_value_setting_none_str(self):
+        """Test the Settings._convert_value() method when a setting is None and the default value is a string"""
+        # GIVEN: A settings object
+        # WHEN: _convert_value() is run
+        result = Settings()._convert_value(None, 'string')
+
+        # THEN: The result should be an empty string
+        assert result == '', 'The result should be an empty string'
+
+    def test_convert_value_setting_none_list(self):
+        """Test the Settings._convert_value() method when a setting is None and the default value is a list"""
+        # GIVEN: A settings object
+        # WHEN: _convert_value() is run
+        result = Settings()._convert_value(None, [None])
+
+        # THEN: The result should be an empty list
+        assert result == [], 'The result should be an empty list'
+
+    def test_convert_value_setting_json_Path(self):
+        """Test the Settings._convert_value() method when a setting is JSON and represents a Path object"""
+        # GIVEN: A settings object
+        # WHEN: _convert_value() is run
+        result = Settings()._convert_value('{"__Path__": ["openlp", "core"]}', None)
+
+        # THEN: The result should be a Path object
+        assert isinstance(result, Path), 'The result should be a Path object'
+
+    def test_convert_value_setting_bool_str(self):
+        """Test the Settings._convert_value() method when a setting is supposed to be a boolean"""
+        # GIVEN: A settings object
+        # WHEN: _convert_value() is run
+        result = Settings()._convert_value('false', True)
+
+        # THEN: The result should be False
+        assert result is False, 'The result should be False'

=== modified file 'tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py'
--- tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py	2017-10-23 22:09:57 +0000
+++ tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py	2017-11-16 05:12:46 +0000
@@ -29,7 +29,6 @@
 from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper
 
 TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..', 'resources', 'media'))
-
 TEST_MEDIA = [['avi_file.avi', 61495], ['mp3_file.mp3', 134426], ['mpg_file.mpg', 9404], ['mp4_file.mp4', 188336]]
 
 


Follow ups