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...

bzr-revno: 2787
This commit is contained in:
Raoul Snyman 2017-11-16 18:52:40 +00:00 committed by Tim Bentley
commit 5d16217a70
4 changed files with 214 additions and 49 deletions

View File

@ -1,5 +1,5 @@
[unittest] [unittest]
verbose = true verbose = True
plugins = nose2.plugins.mp plugins = nose2.plugins.mp
[log-capture] [log-capture]
@ -9,14 +9,19 @@ filter = -nose
log-level = ERROR log-level = ERROR
[test-result] [test-result]
always-on = true always-on = True
descriptions = true descriptions = True
[coverage] [coverage]
always-on = true always-on = True
coverage = openlp coverage = openlp
coverage-report = html coverage-report = html
[multiprocess] [multiprocess]
always-on = false always-on = False
processes = 4 processes = 4
[output-buffer]
always-on = True
stderr = True
stdout = False

View File

@ -40,7 +40,7 @@ __version__ = 2
# Fix for bug #1014422. # Fix for bug #1014422.
X11_BYPASS_DEFAULT = True X11_BYPASS_DEFAULT = True
if is_linux(): if is_linux(): # pragma: no cover
# Default to False on Gnome. # Default to False on Gnome.
X11_BYPASS_DEFAULT = bool(not os.environ.get('GNOME_DESKTOP_SESSION_ID')) X11_BYPASS_DEFAULT = bool(not os.environ.get('GNOME_DESKTOP_SESSION_ID'))
# Default to False on Xfce. # Default to False on Xfce.
@ -206,11 +206,14 @@ class Settings(QtCore.QSettings):
'projector/source dialog type': 0 # Source select dialog box type 'projector/source dialog type': 0 # Source select dialog box type
} }
__file_path__ = '' __file_path__ = ''
# Settings upgrades prior to 3.0
__setting_upgrade_1__ = [ __setting_upgrade_1__ = [
# Changed during 2.2.x development.
('songs/search as type', 'advanced/search as type', []), ('songs/search as type', 'advanced/search as type', []),
('media/players', 'media/players_temp', [(media_players_conv, None)]), # Convert phonon to system ('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 ('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 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. ('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4.
('remotes/https enabled', '', []), ('remotes/https enabled', '', []),
@ -231,9 +234,7 @@ class Settings(QtCore.QSettings):
# Last search type was renamed to last used search type in 2.6 since Bible search value type changed in 2.6. # 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', []), ('songs/last search type', 'songs/last used search type', []),
('bibles/last search type', '', []), ('bibles/last search type', '', []),
('custom/last search type', 'custom/last used search type', [])] ('custom/last search type', 'custom/last used search type', []),
__setting_upgrade_2__ = [
# The following changes are being made for the conversion to using Path objects made in 2.6 development # 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)]), ('advanced/data path', 'advanced/data path', [(str_to_path, None)]),
('crashreport/last directory', 'crashreport/last directory', [(str_to_path, None)]), ('crashreport/last directory', 'crashreport/last directory', [(str_to_path, None)]),
@ -467,32 +468,38 @@ class Settings(QtCore.QSettings):
for version in range(current_version, __version__): for version in range(current_version, __version__):
version += 1 version += 1
upgrade_list = getattr(self, '__setting_upgrade_{version}__'.format(version=version)) 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 # Once removed we don't have to do this again. - Can be removed once fully switched to the versioning
# system. # 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 continue
if new_key: if new_key:
# Get the value of the old_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 # 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. # the default value from the central settings dict.
if rules: if rules:
default_value = rules[0][1] default_values = rules[0][1]
old_value = self._convert_value(old_value, default_value) 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. # 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 # 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. # convert values. E. g. an old value 1 results in True, and 0 in False.
if callable(new): if callable(new_rule):
old_value = new(old_value) new_value = new_rule(*old_values)
elif old == old_value: elif old_rule in old_values:
old_value = new new_value = new_rule
break break
self.setValue(new_key, old_value) self.setValue(new_key, new_value)
if new_key != old_key: [self.remove(old_key) for old_key in old_keys if old_key != new_key]
self.remove(old_key) self.setValue('settings/version', version)
self.setValue('settings/version', version)
def value(self, key): def value(self, key):
""" """

View File

@ -22,10 +22,12 @@
""" """
Package to test the openlp.core.lib.settings package. Package to test the openlp.core.lib.settings package.
""" """
from pathlib import Path
from unittest import TestCase 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 from tests.helpers.testmixin import TestMixin
@ -47,28 +49,58 @@ class TestSettings(TestCase, TestMixin):
""" """
self.destroy_settings() self.destroy_settings()
def test_settings_basic(self): def test_media_players_conv(self):
""" """Test the media players conversion function"""
Test the Settings creation and its default usage # GIVEN: A list of media players
""" media_players = 'phonon,webkit,vlc'
# GIVEN: A new Settings setup
# 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 # WHEN reading a setting for the first time
default_value = Settings().value('core/has run wizard') default_value = Settings().value('core/has run wizard')
# THEN the default value is returned # 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 # WHEN a new value is saved into config
Settings().setValue('core/has run wizard', True) Settings().setValue('core/has run wizard', True)
# THEN the new value is returned when re-read # 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): 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 # GIVEN: an override for the settings
screen_settings = { screen_settings = {
'test/extend': 'very wide', 'test/extend': 'very wide',
@ -79,18 +111,22 @@ class TestSettings(TestCase, TestMixin):
extend = Settings().value('test/extend') extend = Settings().value('test/extend')
# THEN the default value is returned # 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 # 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 # 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): 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 # GIVEN: an override for the settings
screen_settings = { screen_settings = {
'test/extend': 'very wide', 'test/extend': 'very wide',
@ -112,9 +148,7 @@ class TestSettings(TestCase, TestMixin):
self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned') self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')
def test_settings_nonexisting(self): 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 # GIVEN: A new Settings setup
with self.assertRaises(KeyError) as cm: with self.assertRaises(KeyError) as cm:
# WHEN reading a setting that doesn't exists # WHEN reading a setting that doesn't exists
@ -124,9 +158,7 @@ class TestSettings(TestCase, TestMixin):
self.assertEqual(str(cm.exception), "'core/does not exists'", 'We should get an exception') self.assertEqual(str(cm.exception), "'core/does not exists'", 'We should get an exception')
def test_extend_default_settings(self): 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 # GIVEN: A patched __default_settings__ dictionary
with patch.dict(Settings.__default_settings__, with patch.dict(Settings.__default_settings__,
{'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 3}, True): {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 3}, True):
@ -138,3 +170,125 @@ class TestSettings(TestCase, TestMixin):
self.assertEqual( self.assertEqual(
Settings.__default_settings__, {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 4, Settings.__default_settings__, {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 4,
'test/extended 1': 1, 'test/extended 2': 2}) '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'

View File

@ -29,7 +29,6 @@ from unittest import TestCase
from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..', 'resources', 'media')) 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]] TEST_MEDIA = [['avi_file.avi', 61495], ['mp3_file.mp3', 134426], ['mpg_file.mpg', 9404], ['mp4_file.mp4', 188336]]