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

Proposed by Raoul Snyman
Status: Merged
Merged at revision: 2787
Proposed branch: lp:~raoul-snyman/openlp/settings-upgrade
Merge into: lp:openlp
Diff against target: 402 lines (+214/-49)
4 files modified
nose2.cfg (+10/-5)
openlp/core/common/settings.py (+26/-19)
tests/functional/openlp_core/common/test_settings.py (+178/-24)
tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py (+0/-1)
To merge this branch: bzr merge lp:~raoul-snyman/openlp/settings-upgrade
Reviewer Review Type Date Requested Status
Tim Bentley Approve
Phill Approve
Review via email: mp+333788@code.launchpad.net

Commit message

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

Description of the change

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

To post a comment you must log in.
Revision history for this message
Phill (phill-ridout) wrote :

Seems good to me, 1 minor comment, but I'd be happy for this to be merged

review: Approve
Revision history for this message
Tim Bentley (trb143) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== 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 @@
1[unittest]1[unittest]
2verbose = true2verbose = True
3plugins = nose2.plugins.mp3plugins = nose2.plugins.mp
44
5[log-capture]5[log-capture]
@@ -9,14 +9,19 @@
9log-level = ERROR9log-level = ERROR
1010
11[test-result]11[test-result]
12always-on = true12always-on = True
13descriptions = true13descriptions = True
1414
15[coverage]15[coverage]
16always-on = true16always-on = True
17coverage = openlp17coverage = openlp
18coverage-report = html18coverage-report = html
1919
20[multiprocess]20[multiprocess]
21always-on = false21always-on = False
22processes = 422processes = 4
23
24[output-buffer]
25always-on = True
26stderr = True
27stdout = False
2328
=== 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 @@
4040
41# Fix for bug #1014422.41# Fix for bug #1014422.
42X11_BYPASS_DEFAULT = True42X11_BYPASS_DEFAULT = True
43if is_linux():43if is_linux(): # pragma: no cover
44 # Default to False on Gnome.44 # Default to False on Gnome.
45 X11_BYPASS_DEFAULT = bool(not os.environ.get('GNOME_DESKTOP_SESSION_ID'))45 X11_BYPASS_DEFAULT = bool(not os.environ.get('GNOME_DESKTOP_SESSION_ID'))
46 # Default to False on Xfce.46 # Default to False on Xfce.
@@ -206,11 +206,14 @@
206 'projector/source dialog type': 0 # Source select dialog box type206 'projector/source dialog type': 0 # Source select dialog box type
207 }207 }
208 __file_path__ = ''208 __file_path__ = ''
209 # Settings upgrades prior to 3.0
209 __setting_upgrade_1__ = [210 __setting_upgrade_1__ = [
210 # Changed during 2.2.x development.
211 ('songs/search as type', 'advanced/search as type', []),211 ('songs/search as type', 'advanced/search as type', []),
212 ('media/players', 'media/players_temp', [(media_players_conv, None)]), # Convert phonon to system212 ('media/players', 'media/players_temp', [(media_players_conv, None)]), # Convert phonon to system
213 ('media/players_temp', 'media/players', []), # Move temp setting from above to correct setting213 ('media/players_temp', 'media/players', []), # Move temp setting from above to correct setting
214 ]
215 # Settings upgrades for 3.0 (aka 2.6)
216 __setting_upgrade_2__ = [
214 ('advanced/default color', 'core/logo background color', []), # Default image renamed + moved to general > 2.4.217 ('advanced/default color', 'core/logo background color', []), # Default image renamed + moved to general > 2.4.
215 ('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4.218 ('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4.
216 ('remotes/https enabled', '', []),219 ('remotes/https enabled', '', []),
@@ -231,9 +234,7 @@
231 # Last search type was renamed to last used search type in 2.6 since Bible search value type changed in 2.6.234 # Last search type was renamed to last used search type in 2.6 since Bible search value type changed in 2.6.
232 ('songs/last search type', 'songs/last used search type', []),235 ('songs/last search type', 'songs/last used search type', []),
233 ('bibles/last search type', '', []),236 ('bibles/last search type', '', []),
234 ('custom/last search type', 'custom/last used search type', [])]237 ('custom/last search type', 'custom/last used search type', []),
235
236 __setting_upgrade_2__ = [
237 # The following changes are being made for the conversion to using Path objects made in 2.6 development238 # The following changes are being made for the conversion to using Path objects made in 2.6 development
238 ('advanced/data path', 'advanced/data path', [(str_to_path, None)]),239 ('advanced/data path', 'advanced/data path', [(str_to_path, None)]),
239 ('crashreport/last directory', 'crashreport/last directory', [(str_to_path, None)]),240 ('crashreport/last directory', 'crashreport/last directory', [(str_to_path, None)]),
@@ -467,32 +468,38 @@
467 for version in range(current_version, __version__):468 for version in range(current_version, __version__):
468 version += 1469 version += 1
469 upgrade_list = getattr(self, '__setting_upgrade_{version}__'.format(version=version))470 upgrade_list = getattr(self, '__setting_upgrade_{version}__'.format(version=version))
470 for old_key, new_key, rules in upgrade_list:471 for old_keys, new_key, rules in upgrade_list:
471 # Once removed we don't have to do this again. - Can be removed once fully switched to the versioning472 # Once removed we don't have to do this again. - Can be removed once fully switched to the versioning
472 # system.473 # system.
473 if not self.contains(old_key):474 if not isinstance(old_keys, (tuple, list)):
475 old_keys = [old_keys]
476 if any([not self.contains(old_key) for old_key in old_keys]):
477 log.warning('One of {} does not exist, skipping upgrade'.format(old_keys))
474 continue478 continue
475 if new_key:479 if new_key:
476 # Get the value of the old_key.480 # Get the value of the old_key.
477 old_value = super(Settings, self).value(old_key)481 old_values = [super(Settings, self).value(old_key) for old_key in old_keys]
478 # When we want to convert the value, we have to figure out the default value (because we cannot get482 # When we want to convert the value, we have to figure out the default value (because we cannot get
479 # the default value from the central settings dict.483 # the default value from the central settings dict.
480 if rules:484 if rules:
481 default_value = rules[0][1]485 default_values = rules[0][1]
482 old_value = self._convert_value(old_value, default_value)486 if not isinstance(default_values, (list, tuple)):
487 default_values = [default_values]
488 old_values = [self._convert_value(old_value, default_value)
489 for old_value, default_value in zip(old_values, default_values)]
483 # Iterate over our rules and check what the old_value should be "converted" to.490 # Iterate over our rules and check what the old_value should be "converted" to.
484 for new, old in rules:491 new_value = None
492 for new_rule, old_rule in rules:
485 # If the value matches with the condition (rule), then use the provided value. This is used to493 # If the value matches with the condition (rule), then use the provided value. This is used to
486 # convert values. E. g. an old value 1 results in True, and 0 in False.494 # convert values. E. g. an old value 1 results in True, and 0 in False.
487 if callable(new):495 if callable(new_rule):
488 old_value = new(old_value)496 new_value = new_rule(*old_values)
489 elif old == old_value:497 elif old_rule in old_values:
490 old_value = new498 new_value = new_rule
491 break499 break
492 self.setValue(new_key, old_value)500 self.setValue(new_key, new_value)
493 if new_key != old_key:501 [self.remove(old_key) for old_key in old_keys if old_key != new_key]
494 self.remove(old_key)502 self.setValue('settings/version', version)
495 self.setValue('settings/version', version)
496503
497 def value(self, key):504 def value(self, key):
498 """505 """
499506
=== 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 @@
22"""22"""
23Package to test the openlp.core.lib.settings package.23Package to test the openlp.core.lib.settings package.
24"""24"""
25from pathlib import Path
25from unittest import TestCase26from unittest import TestCase
26from unittest.mock import patch27from unittest.mock import call, patch
2728
28from openlp.core.common.settings import Settings29from openlp.core.common import settings
30from openlp.core.common.settings import Settings, media_players_conv
2931
30from tests.helpers.testmixin import TestMixin32from tests.helpers.testmixin import TestMixin
3133
@@ -47,28 +49,58 @@
47 """49 """
48 self.destroy_settings()50 self.destroy_settings()
4951
50 def test_settings_basic(self):52 def test_media_players_conv(self):
51 """53 """Test the media players conversion function"""
52 Test the Settings creation and its default usage54 # GIVEN: A list of media players
53 """55 media_players = 'phonon,webkit,vlc'
54 # GIVEN: A new Settings setup56
57 # WHEN: The media converter function is called
58 result = media_players_conv(media_players)
59
60 # THEN: The list should have been converted correctly
61 assert result == 'system,webkit,vlc'
62
63 def test_default_value(self):
64 """Test reading a setting that doesn't exist yet"""
65 # GIVEN: A setting that doesn't exist yet
5566
56 # WHEN reading a setting for the first time67 # WHEN reading a setting for the first time
57 default_value = Settings().value('core/has run wizard')68 default_value = Settings().value('core/has run wizard')
5869
59 # THEN the default value is returned70 # THEN the default value is returned
60 self.assertFalse(default_value, 'The default value should be False')71 assert default_value is False, 'The default value should be False'
6172
73 def test_save_new_value(self):
74 """Test saving a new setting"""
75 # GIVEN: A setting that hasn't been saved yet
62 # WHEN a new value is saved into config76 # WHEN a new value is saved into config
63 Settings().setValue('core/has run wizard', True)77 Settings().setValue('core/has run wizard', True)
6478
65 # THEN the new value is returned when re-read79 # THEN the new value is returned when re-read
66 self.assertTrue(Settings().value('core/has run wizard'), 'The saved value should have been returned')80 assert Settings().value('core/has run wizard') is True, 'The saved value should have been returned'
81
82 def test_set_up_default_values(self):
83 """Test that the default values are updated"""
84 # GIVEN: A Settings object with defaults
85 # WHEN: set_up_default_values() is called
86 Settings.set_up_default_values()
87
88 # THEN: The default values should have been added to the dictionary
89 assert 'advanced/default service name' in Settings.__default_settings__
90
91 def test_get_default_value(self):
92 """Test that the default value for a setting is returned"""
93 # GIVEN: A Settings class with a default value
94 Settings.__default_settings__['test/moo'] = 'baa'
95
96 # WHEN: get_default_value() is called
97 result = Settings().get_default_value('test/moo')
98
99 # THEN: The correct default value should be returned
100 assert result == 'baa'
67101
68 def test_settings_override(self):102 def test_settings_override(self):
69 """103 """Test the Settings creation and its override usage"""
70 Test the Settings creation and its override usage
71 """
72 # GIVEN: an override for the settings104 # GIVEN: an override for the settings
73 screen_settings = {105 screen_settings = {
74 'test/extend': 'very wide',106 'test/extend': 'very wide',
@@ -79,18 +111,22 @@
79 extend = Settings().value('test/extend')111 extend = Settings().value('test/extend')
80112
81 # THEN the default value is returned113 # THEN the default value is returned
82 self.assertEqual('very wide', extend, 'The default value of "very wide" should be returned')114 assert extend == 'very wide', 'The default value of "very wide" should be returned'
115
116 def test_save_existing_setting(self):
117 """Test that saving an existing setting returns the new value"""
118 # GIVEN: An existing setting
119 Settings().extend_default_settings({'test/existing value': None})
120 Settings().setValue('test/existing value', 'old value')
83121
84 # WHEN a new value is saved into config122 # WHEN a new value is saved into config
85 Settings().setValue('test/extend', 'very short')123 Settings().setValue('test/existing value', 'new value')
86124
87 # THEN the new value is returned when re-read125 # THEN the new value is returned when re-read
88 self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')126 assert Settings().value('test/existing value') == 'new value', 'The saved value should be returned'
89127
90 def test_settings_override_with_group(self):128 def test_settings_override_with_group(self):
91 """129 """Test the Settings creation and its override usage - with groups"""
92 Test the Settings creation and its override usage - with groups
93 """
94 # GIVEN: an override for the settings130 # GIVEN: an override for the settings
95 screen_settings = {131 screen_settings = {
96 'test/extend': 'very wide',132 'test/extend': 'very wide',
@@ -112,9 +148,7 @@
112 self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')148 self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')
113149
114 def test_settings_nonexisting(self):150 def test_settings_nonexisting(self):
115 """151 """Test the Settings on query for non-existing value"""
116 Test the Settings on query for non-existing value
117 """
118 # GIVEN: A new Settings setup152 # GIVEN: A new Settings setup
119 with self.assertRaises(KeyError) as cm:153 with self.assertRaises(KeyError) as cm:
120 # WHEN reading a setting that doesn't exists154 # WHEN reading a setting that doesn't exists
@@ -124,9 +158,7 @@
124 self.assertEqual(str(cm.exception), "'core/does not exists'", 'We should get an exception')158 self.assertEqual(str(cm.exception), "'core/does not exists'", 'We should get an exception')
125159
126 def test_extend_default_settings(self):160 def test_extend_default_settings(self):
127 """161 """Test that the extend_default_settings method extends the default settings"""
128 Test that the extend_default_settings method extends the default settings
129 """
130 # GIVEN: A patched __default_settings__ dictionary162 # GIVEN: A patched __default_settings__ dictionary
131 with patch.dict(Settings.__default_settings__,163 with patch.dict(Settings.__default_settings__,
132 {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 3}, True):164 {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 3}, True):
@@ -138,3 +170,125 @@
138 self.assertEqual(170 self.assertEqual(
139 Settings.__default_settings__, {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 4,171 Settings.__default_settings__, {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 4,
140 'test/extended 1': 1, 'test/extended 2': 2})172 'test/extended 1': 1, 'test/extended 2': 2})
173
174 @patch('openlp.core.common.settings.QtCore.QSettings.contains')
175 @patch('openlp.core.common.settings.QtCore.QSettings.value')
176 @patch('openlp.core.common.settings.QtCore.QSettings.setValue')
177 @patch('openlp.core.common.settings.QtCore.QSettings.remove')
178 def test_upgrade_single_setting(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains):
179 """Test that the upgrade mechanism for settings works correctly for single value upgrades"""
180 # GIVEN: A settings object with an upgrade step to take (99, so that we don't interfere with real ones)
181 local_settings = Settings()
182 local_settings.__setting_upgrade_99__ = [
183 ('single/value', 'single/new value', [(str, '')])
184 ]
185 settings.__version__ = 99
186 mocked_value.side_effect = [98, 10]
187 mocked_contains.return_value = True
188
189 # WHEN: upgrade_settings() is called
190 local_settings.upgrade_settings()
191
192 # THEN: The correct calls should have been made with the correct values
193 assert mocked_value.call_count == 2, 'Settings().value() should have been called twice'
194 assert mocked_value.call_args_list == [call('settings/version', 0), call('single/value')]
195 assert mocked_setValue.call_count == 2, 'Settings().setValue() should have been called twice'
196 assert mocked_setValue.call_args_list == [call('single/new value', '10'), call('settings/version', 99)]
197 mocked_contains.assert_called_once_with('single/value')
198 mocked_remove.assert_called_once_with('single/value')
199
200 @patch('openlp.core.common.settings.QtCore.QSettings.contains')
201 @patch('openlp.core.common.settings.QtCore.QSettings.value')
202 @patch('openlp.core.common.settings.QtCore.QSettings.setValue')
203 @patch('openlp.core.common.settings.QtCore.QSettings.remove')
204 def test_upgrade_setting_value(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains):
205 """Test that the upgrade mechanism for settings correctly uses the new value when it's not a function"""
206 # GIVEN: A settings object with an upgrade step to take (99, so that we don't interfere with real ones)
207 local_settings = Settings()
208 local_settings.__setting_upgrade_99__ = [
209 ('values/old value', 'values/new value', [(True, 1)])
210 ]
211 settings.__version__ = 99
212 mocked_value.side_effect = [98, 1]
213 mocked_contains.return_value = True
214
215 # WHEN: upgrade_settings() is called
216 local_settings.upgrade_settings()
217
218 # THEN: The correct calls should have been made with the correct values
219 assert mocked_value.call_count == 2, 'Settings().value() should have been called twice'
220 assert mocked_value.call_args_list == [call('settings/version', 0), call('values/old value')]
221 assert mocked_setValue.call_count == 2, 'Settings().setValue() should have been called twice'
222 assert mocked_setValue.call_args_list == [call('values/new value', True), call('settings/version', 99)]
223 mocked_contains.assert_called_once_with('values/old value')
224 mocked_remove.assert_called_once_with('values/old value')
225
226 @patch('openlp.core.common.settings.QtCore.QSettings.contains')
227 @patch('openlp.core.common.settings.QtCore.QSettings.value')
228 @patch('openlp.core.common.settings.QtCore.QSettings.setValue')
229 @patch('openlp.core.common.settings.QtCore.QSettings.remove')
230 def test_upgrade_multiple_one_invalid(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains):
231 """Test that the upgrade mechanism for settings works correctly for multiple values where one is invalid"""
232 # GIVEN: A settings object with an upgrade step to take
233 local_settings = Settings()
234 local_settings.__setting_upgrade_99__ = [
235 (['multiple/value 1', 'multiple/value 2'], 'single/new value', [])
236 ]
237 settings.__version__ = 99
238 mocked_value.side_effect = [98, 10]
239 mocked_contains.side_effect = [True, False]
240
241 # WHEN: upgrade_settings() is called
242 local_settings.upgrade_settings()
243
244 # THEN: The correct calls should have been made with the correct values
245 mocked_value.assert_called_once_with('settings/version', 0)
246 mocked_setValue.assert_called_once_with('settings/version', 99)
247 assert mocked_contains.call_args_list == [call('multiple/value 1'), call('multiple/value 2')]
248
249 def test_can_upgrade(self):
250 """Test the Settings.can_upgrade() method"""
251 # GIVEN: A Settings object
252 local_settings = Settings()
253
254 # WHEN: can_upgrade() is run
255 result = local_settings.can_upgrade()
256
257 # THEN: The result should be True
258 assert result is True, 'The settings should be upgradeable'
259
260 def test_convert_value_setting_none_str(self):
261 """Test the Settings._convert_value() method when a setting is None and the default value is a string"""
262 # GIVEN: A settings object
263 # WHEN: _convert_value() is run
264 result = Settings()._convert_value(None, 'string')
265
266 # THEN: The result should be an empty string
267 assert result == '', 'The result should be an empty string'
268
269 def test_convert_value_setting_none_list(self):
270 """Test the Settings._convert_value() method when a setting is None and the default value is a list"""
271 # GIVEN: A settings object
272 # WHEN: _convert_value() is run
273 result = Settings()._convert_value(None, [None])
274
275 # THEN: The result should be an empty list
276 assert result == [], 'The result should be an empty list'
277
278 def test_convert_value_setting_json_Path(self):
279 """Test the Settings._convert_value() method when a setting is JSON and represents a Path object"""
280 # GIVEN: A settings object
281 # WHEN: _convert_value() is run
282 result = Settings()._convert_value('{"__Path__": ["openlp", "core"]}', None)
283
284 # THEN: The result should be a Path object
285 assert isinstance(result, Path), 'The result should be a Path object'
286
287 def test_convert_value_setting_bool_str(self):
288 """Test the Settings._convert_value() method when a setting is supposed to be a boolean"""
289 # GIVEN: A settings object
290 # WHEN: _convert_value() is run
291 result = Settings()._convert_value('false', True)
292
293 # THEN: The result should be False
294 assert result is False, 'The result should be False'
141295
=== 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 @@
29from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper29from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper
3030
31TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..', 'resources', 'media'))31TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..', 'resources', 'media'))
32
33TEST_MEDIA = [['avi_file.avi', 61495], ['mp3_file.mp3', 134426], ['mpg_file.mpg', 9404], ['mp4_file.mp4', 188336]]32TEST_MEDIA = [['avi_file.avi', 61495], ['mp3_file.mp3', 134426], ['mpg_file.mpg', 9404], ['mp4_file.mp4', 188336]]
3433
3534