Merge lp:~raoul-snyman/openlp/settings-upgrade into lp:openlp
- settings-upgrade
- Merge into trunk
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 |
Related bugs: |
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_
Description of the change
Add multi-to-one settings upgrading, and merge all the post-2.4 settings changes into __setting_
Add this to your merge proposal:
-------
lp:~raoul-snyman/openlp/settings-upgrade (revision 2791)
https:/
https:/
https:/
https:/
https:/
https:/
https:/
Stopping after failure, see https:/
Tim Bentley (trb143) : | # |
Preview Diff
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 |
Seems good to me, 1 minor comment, but I'd be happy for this to be merged