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
1=== modified file 'nose2.cfg'
2--- nose2.cfg 2016-08-10 09:07:49 +0000
3+++ nose2.cfg 2017-11-16 05:12:46 +0000
4@@ -1,5 +1,5 @@
5 [unittest]
6-verbose = true
7+verbose = True
8 plugins = nose2.plugins.mp
9
10 [log-capture]
11@@ -9,14 +9,19 @@
12 log-level = ERROR
13
14 [test-result]
15-always-on = true
16-descriptions = true
17+always-on = True
18+descriptions = True
19
20 [coverage]
21-always-on = true
22+always-on = True
23 coverage = openlp
24 coverage-report = html
25
26 [multiprocess]
27-always-on = false
28+always-on = False
29 processes = 4
30+
31+[output-buffer]
32+always-on = True
33+stderr = True
34+stdout = False
35
36=== modified file 'openlp/core/common/settings.py'
37--- openlp/core/common/settings.py 2017-11-10 22:50:04 +0000
38+++ openlp/core/common/settings.py 2017-11-16 05:12:46 +0000
39@@ -40,7 +40,7 @@
40
41 # Fix for bug #1014422.
42 X11_BYPASS_DEFAULT = True
43-if is_linux():
44+if is_linux(): # pragma: no cover
45 # Default to False on Gnome.
46 X11_BYPASS_DEFAULT = bool(not os.environ.get('GNOME_DESKTOP_SESSION_ID'))
47 # Default to False on Xfce.
48@@ -206,11 +206,14 @@
49 'projector/source dialog type': 0 # Source select dialog box type
50 }
51 __file_path__ = ''
52+ # Settings upgrades prior to 3.0
53 __setting_upgrade_1__ = [
54- # Changed during 2.2.x development.
55 ('songs/search as type', 'advanced/search as type', []),
56 ('media/players', 'media/players_temp', [(media_players_conv, None)]), # Convert phonon to system
57 ('media/players_temp', 'media/players', []), # Move temp setting from above to correct setting
58+ ]
59+ # Settings upgrades for 3.0 (aka 2.6)
60+ __setting_upgrade_2__ = [
61 ('advanced/default color', 'core/logo background color', []), # Default image renamed + moved to general > 2.4.
62 ('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4.
63 ('remotes/https enabled', '', []),
64@@ -231,9 +234,7 @@
65 # Last search type was renamed to last used search type in 2.6 since Bible search value type changed in 2.6.
66 ('songs/last search type', 'songs/last used search type', []),
67 ('bibles/last search type', '', []),
68- ('custom/last search type', 'custom/last used search type', [])]
69-
70- __setting_upgrade_2__ = [
71+ ('custom/last search type', 'custom/last used search type', []),
72 # The following changes are being made for the conversion to using Path objects made in 2.6 development
73 ('advanced/data path', 'advanced/data path', [(str_to_path, None)]),
74 ('crashreport/last directory', 'crashreport/last directory', [(str_to_path, None)]),
75@@ -467,32 +468,38 @@
76 for version in range(current_version, __version__):
77 version += 1
78 upgrade_list = getattr(self, '__setting_upgrade_{version}__'.format(version=version))
79- for old_key, new_key, rules in upgrade_list:
80+ for old_keys, new_key, rules in upgrade_list:
81 # Once removed we don't have to do this again. - Can be removed once fully switched to the versioning
82 # system.
83- if not self.contains(old_key):
84+ if not isinstance(old_keys, (tuple, list)):
85+ old_keys = [old_keys]
86+ if any([not self.contains(old_key) for old_key in old_keys]):
87+ log.warning('One of {} does not exist, skipping upgrade'.format(old_keys))
88 continue
89 if new_key:
90 # Get the value of the old_key.
91- old_value = super(Settings, self).value(old_key)
92+ old_values = [super(Settings, self).value(old_key) for old_key in old_keys]
93 # When we want to convert the value, we have to figure out the default value (because we cannot get
94 # the default value from the central settings dict.
95 if rules:
96- default_value = rules[0][1]
97- old_value = self._convert_value(old_value, default_value)
98+ default_values = rules[0][1]
99+ if not isinstance(default_values, (list, tuple)):
100+ default_values = [default_values]
101+ old_values = [self._convert_value(old_value, default_value)
102+ for old_value, default_value in zip(old_values, default_values)]
103 # Iterate over our rules and check what the old_value should be "converted" to.
104- for new, old in rules:
105+ new_value = None
106+ for new_rule, old_rule in rules:
107 # If the value matches with the condition (rule), then use the provided value. This is used to
108 # convert values. E. g. an old value 1 results in True, and 0 in False.
109- if callable(new):
110- old_value = new(old_value)
111- elif old == old_value:
112- old_value = new
113+ if callable(new_rule):
114+ new_value = new_rule(*old_values)
115+ elif old_rule in old_values:
116+ new_value = new_rule
117 break
118- self.setValue(new_key, old_value)
119- if new_key != old_key:
120- self.remove(old_key)
121- self.setValue('settings/version', version)
122+ self.setValue(new_key, new_value)
123+ [self.remove(old_key) for old_key in old_keys if old_key != new_key]
124+ self.setValue('settings/version', version)
125
126 def value(self, key):
127 """
128
129=== modified file 'tests/functional/openlp_core/common/test_settings.py'
130--- tests/functional/openlp_core/common/test_settings.py 2017-10-07 07:05:07 +0000
131+++ tests/functional/openlp_core/common/test_settings.py 2017-11-16 05:12:46 +0000
132@@ -22,10 +22,12 @@
133 """
134 Package to test the openlp.core.lib.settings package.
135 """
136+from pathlib import Path
137 from unittest import TestCase
138-from unittest.mock import patch
139+from unittest.mock import call, patch
140
141-from openlp.core.common.settings import Settings
142+from openlp.core.common import settings
143+from openlp.core.common.settings import Settings, media_players_conv
144
145 from tests.helpers.testmixin import TestMixin
146
147@@ -47,28 +49,58 @@
148 """
149 self.destroy_settings()
150
151- def test_settings_basic(self):
152- """
153- Test the Settings creation and its default usage
154- """
155- # GIVEN: A new Settings setup
156+ def test_media_players_conv(self):
157+ """Test the media players conversion function"""
158+ # GIVEN: A list of media players
159+ media_players = 'phonon,webkit,vlc'
160+
161+ # WHEN: The media converter function is called
162+ result = media_players_conv(media_players)
163+
164+ # THEN: The list should have been converted correctly
165+ assert result == 'system,webkit,vlc'
166+
167+ def test_default_value(self):
168+ """Test reading a setting that doesn't exist yet"""
169+ # GIVEN: A setting that doesn't exist yet
170
171 # WHEN reading a setting for the first time
172 default_value = Settings().value('core/has run wizard')
173
174 # THEN the default value is returned
175- self.assertFalse(default_value, 'The default value should be False')
176+ assert default_value is False, 'The default value should be False'
177
178+ def test_save_new_value(self):
179+ """Test saving a new setting"""
180+ # GIVEN: A setting that hasn't been saved yet
181 # WHEN a new value is saved into config
182 Settings().setValue('core/has run wizard', True)
183
184 # THEN the new value is returned when re-read
185- self.assertTrue(Settings().value('core/has run wizard'), 'The saved value should have been returned')
186+ assert Settings().value('core/has run wizard') is True, 'The saved value should have been returned'
187+
188+ def test_set_up_default_values(self):
189+ """Test that the default values are updated"""
190+ # GIVEN: A Settings object with defaults
191+ # WHEN: set_up_default_values() is called
192+ Settings.set_up_default_values()
193+
194+ # THEN: The default values should have been added to the dictionary
195+ assert 'advanced/default service name' in Settings.__default_settings__
196+
197+ def test_get_default_value(self):
198+ """Test that the default value for a setting is returned"""
199+ # GIVEN: A Settings class with a default value
200+ Settings.__default_settings__['test/moo'] = 'baa'
201+
202+ # WHEN: get_default_value() is called
203+ result = Settings().get_default_value('test/moo')
204+
205+ # THEN: The correct default value should be returned
206+ assert result == 'baa'
207
208 def test_settings_override(self):
209- """
210- Test the Settings creation and its override usage
211- """
212+ """Test the Settings creation and its override usage"""
213 # GIVEN: an override for the settings
214 screen_settings = {
215 'test/extend': 'very wide',
216@@ -79,18 +111,22 @@
217 extend = Settings().value('test/extend')
218
219 # THEN the default value is returned
220- self.assertEqual('very wide', extend, 'The default value of "very wide" should be returned')
221+ assert extend == 'very wide', 'The default value of "very wide" should be returned'
222+
223+ def test_save_existing_setting(self):
224+ """Test that saving an existing setting returns the new value"""
225+ # GIVEN: An existing setting
226+ Settings().extend_default_settings({'test/existing value': None})
227+ Settings().setValue('test/existing value', 'old value')
228
229 # WHEN a new value is saved into config
230- Settings().setValue('test/extend', 'very short')
231+ Settings().setValue('test/existing value', 'new value')
232
233 # THEN the new value is returned when re-read
234- self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')
235+ assert Settings().value('test/existing value') == 'new value', 'The saved value should be returned'
236
237 def test_settings_override_with_group(self):
238- """
239- Test the Settings creation and its override usage - with groups
240- """
241+ """Test the Settings creation and its override usage - with groups"""
242 # GIVEN: an override for the settings
243 screen_settings = {
244 'test/extend': 'very wide',
245@@ -112,9 +148,7 @@
246 self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')
247
248 def test_settings_nonexisting(self):
249- """
250- Test the Settings on query for non-existing value
251- """
252+ """Test the Settings on query for non-existing value"""
253 # GIVEN: A new Settings setup
254 with self.assertRaises(KeyError) as cm:
255 # WHEN reading a setting that doesn't exists
256@@ -124,9 +158,7 @@
257 self.assertEqual(str(cm.exception), "'core/does not exists'", 'We should get an exception')
258
259 def test_extend_default_settings(self):
260- """
261- Test that the extend_default_settings method extends the default settings
262- """
263+ """Test that the extend_default_settings method extends the default settings"""
264 # GIVEN: A patched __default_settings__ dictionary
265 with patch.dict(Settings.__default_settings__,
266 {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 3}, True):
267@@ -138,3 +170,125 @@
268 self.assertEqual(
269 Settings.__default_settings__, {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 4,
270 'test/extended 1': 1, 'test/extended 2': 2})
271+
272+ @patch('openlp.core.common.settings.QtCore.QSettings.contains')
273+ @patch('openlp.core.common.settings.QtCore.QSettings.value')
274+ @patch('openlp.core.common.settings.QtCore.QSettings.setValue')
275+ @patch('openlp.core.common.settings.QtCore.QSettings.remove')
276+ def test_upgrade_single_setting(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains):
277+ """Test that the upgrade mechanism for settings works correctly for single value upgrades"""
278+ # GIVEN: A settings object with an upgrade step to take (99, so that we don't interfere with real ones)
279+ local_settings = Settings()
280+ local_settings.__setting_upgrade_99__ = [
281+ ('single/value', 'single/new value', [(str, '')])
282+ ]
283+ settings.__version__ = 99
284+ mocked_value.side_effect = [98, 10]
285+ mocked_contains.return_value = True
286+
287+ # WHEN: upgrade_settings() is called
288+ local_settings.upgrade_settings()
289+
290+ # THEN: The correct calls should have been made with the correct values
291+ assert mocked_value.call_count == 2, 'Settings().value() should have been called twice'
292+ assert mocked_value.call_args_list == [call('settings/version', 0), call('single/value')]
293+ assert mocked_setValue.call_count == 2, 'Settings().setValue() should have been called twice'
294+ assert mocked_setValue.call_args_list == [call('single/new value', '10'), call('settings/version', 99)]
295+ mocked_contains.assert_called_once_with('single/value')
296+ mocked_remove.assert_called_once_with('single/value')
297+
298+ @patch('openlp.core.common.settings.QtCore.QSettings.contains')
299+ @patch('openlp.core.common.settings.QtCore.QSettings.value')
300+ @patch('openlp.core.common.settings.QtCore.QSettings.setValue')
301+ @patch('openlp.core.common.settings.QtCore.QSettings.remove')
302+ def test_upgrade_setting_value(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains):
303+ """Test that the upgrade mechanism for settings correctly uses the new value when it's not a function"""
304+ # GIVEN: A settings object with an upgrade step to take (99, so that we don't interfere with real ones)
305+ local_settings = Settings()
306+ local_settings.__setting_upgrade_99__ = [
307+ ('values/old value', 'values/new value', [(True, 1)])
308+ ]
309+ settings.__version__ = 99
310+ mocked_value.side_effect = [98, 1]
311+ mocked_contains.return_value = True
312+
313+ # WHEN: upgrade_settings() is called
314+ local_settings.upgrade_settings()
315+
316+ # THEN: The correct calls should have been made with the correct values
317+ assert mocked_value.call_count == 2, 'Settings().value() should have been called twice'
318+ assert mocked_value.call_args_list == [call('settings/version', 0), call('values/old value')]
319+ assert mocked_setValue.call_count == 2, 'Settings().setValue() should have been called twice'
320+ assert mocked_setValue.call_args_list == [call('values/new value', True), call('settings/version', 99)]
321+ mocked_contains.assert_called_once_with('values/old value')
322+ mocked_remove.assert_called_once_with('values/old value')
323+
324+ @patch('openlp.core.common.settings.QtCore.QSettings.contains')
325+ @patch('openlp.core.common.settings.QtCore.QSettings.value')
326+ @patch('openlp.core.common.settings.QtCore.QSettings.setValue')
327+ @patch('openlp.core.common.settings.QtCore.QSettings.remove')
328+ def test_upgrade_multiple_one_invalid(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains):
329+ """Test that the upgrade mechanism for settings works correctly for multiple values where one is invalid"""
330+ # GIVEN: A settings object with an upgrade step to take
331+ local_settings = Settings()
332+ local_settings.__setting_upgrade_99__ = [
333+ (['multiple/value 1', 'multiple/value 2'], 'single/new value', [])
334+ ]
335+ settings.__version__ = 99
336+ mocked_value.side_effect = [98, 10]
337+ mocked_contains.side_effect = [True, False]
338+
339+ # WHEN: upgrade_settings() is called
340+ local_settings.upgrade_settings()
341+
342+ # THEN: The correct calls should have been made with the correct values
343+ mocked_value.assert_called_once_with('settings/version', 0)
344+ mocked_setValue.assert_called_once_with('settings/version', 99)
345+ assert mocked_contains.call_args_list == [call('multiple/value 1'), call('multiple/value 2')]
346+
347+ def test_can_upgrade(self):
348+ """Test the Settings.can_upgrade() method"""
349+ # GIVEN: A Settings object
350+ local_settings = Settings()
351+
352+ # WHEN: can_upgrade() is run
353+ result = local_settings.can_upgrade()
354+
355+ # THEN: The result should be True
356+ assert result is True, 'The settings should be upgradeable'
357+
358+ def test_convert_value_setting_none_str(self):
359+ """Test the Settings._convert_value() method when a setting is None and the default value is a string"""
360+ # GIVEN: A settings object
361+ # WHEN: _convert_value() is run
362+ result = Settings()._convert_value(None, 'string')
363+
364+ # THEN: The result should be an empty string
365+ assert result == '', 'The result should be an empty string'
366+
367+ def test_convert_value_setting_none_list(self):
368+ """Test the Settings._convert_value() method when a setting is None and the default value is a list"""
369+ # GIVEN: A settings object
370+ # WHEN: _convert_value() is run
371+ result = Settings()._convert_value(None, [None])
372+
373+ # THEN: The result should be an empty list
374+ assert result == [], 'The result should be an empty list'
375+
376+ def test_convert_value_setting_json_Path(self):
377+ """Test the Settings._convert_value() method when a setting is JSON and represents a Path object"""
378+ # GIVEN: A settings object
379+ # WHEN: _convert_value() is run
380+ result = Settings()._convert_value('{"__Path__": ["openlp", "core"]}', None)
381+
382+ # THEN: The result should be a Path object
383+ assert isinstance(result, Path), 'The result should be a Path object'
384+
385+ def test_convert_value_setting_bool_str(self):
386+ """Test the Settings._convert_value() method when a setting is supposed to be a boolean"""
387+ # GIVEN: A settings object
388+ # WHEN: _convert_value() is run
389+ result = Settings()._convert_value('false', True)
390+
391+ # THEN: The result should be False
392+ assert result is False, 'The result should be False'
393
394=== modified file 'tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py'
395--- tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py 2017-10-23 22:09:57 +0000
396+++ tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py 2017-11-16 05:12:46 +0000
397@@ -29,7 +29,6 @@
398 from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper
399
400 TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..', 'resources', 'media'))
401-
402 TEST_MEDIA = [['avi_file.avi', 61495], ['mp3_file.mp3', 134426], ['mpg_file.mpg', 9404], ['mp4_file.mp4', 188336]]
403
404