Merge lp:~nataliabidart/ubuntuone-control-panel/handle-errors into lp:ubuntuone-control-panel
- handle-errors
- Merge into trunk
Proposed by
Natalia Bidart
Status: | Merged |
---|---|
Approved by: | Roberto Alsina |
Approved revision: | 216 |
Merged at revision: | 218 |
Proposed branch: | lp:~nataliabidart/ubuntuone-control-panel/handle-errors |
Merge into: | lp:ubuntuone-control-panel |
Diff against target: |
1063 lines (+396/-127) 21 files modified
data/qt/signin.ui (+0/-7) ubuntuone/controlpanel/cache.py (+7/-2) ubuntuone/controlpanel/gui/qt/__init__.py (+49/-0) ubuntuone/controlpanel/gui/qt/account.py (+1/-0) ubuntuone/controlpanel/gui/qt/addfolder.py (+2/-0) ubuntuone/controlpanel/gui/qt/controlpanel.py (+1/-0) ubuntuone/controlpanel/gui/qt/device.py (+11/-2) ubuntuone/controlpanel/gui/qt/devices.py (+1/-0) ubuntuone/controlpanel/gui/qt/folders.py (+1/-0) ubuntuone/controlpanel/gui/qt/preferences.py (+1/-0) ubuntuone/controlpanel/gui/qt/signin.py (+9/-10) ubuntuone/controlpanel/gui/qt/tests/__init__.py (+43/-5) ubuntuone/controlpanel/gui/qt/tests/test_addfolder.py (+20/-23) ubuntuone/controlpanel/gui/qt/tests/test_common.py (+166/-3) ubuntuone/controlpanel/gui/qt/tests/test_device.py (+19/-11) ubuntuone/controlpanel/gui/qt/tests/test_devices.py (+0/-2) ubuntuone/controlpanel/gui/qt/tests/test_folders.py (+14/-31) ubuntuone/controlpanel/gui/qt/tests/test_signin.py (+14/-30) ubuntuone/controlpanel/gui/qt/tests/test_ubuntuonebin.py (+19/-1) ubuntuone/controlpanel/gui/qt/ubuntuonebin.py (+11/-0) ubuntuone/controlpanel/tests/test_cache.py (+7/-0) |
To merge this branch: | bzr merge lp:~nataliabidart/ubuntuone-control-panel/handle-errors |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Roberto Alsina (community) | Approve | ||
Diego Sarmentero (community) | Approve | ||
Review via email: mp+74490@code.launchpad.net |
Commit message
- Added decorator to handle errors from the backend (LP: #807021).
Description of the change
To post a comment you must log in.
- 216. By Natalia Bidart
-
Forgotten is_processing = False.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'data/qt/signin.ui' |
2 | --- data/qt/signin.ui 2011-08-30 23:18:35 +0000 |
3 | +++ data/qt/signin.ui 2011-09-08 01:17:23 +0000 |
4 | @@ -41,13 +41,6 @@ |
5 | </widget> |
6 | </item> |
7 | <item> |
8 | - <widget class="QLabel" name="warning_label"> |
9 | - <property name="text"> |
10 | - <string>warning label!</string> |
11 | - </property> |
12 | - </widget> |
13 | - </item> |
14 | - <item> |
15 | <layout class="QHBoxLayout" name="horizontalLayout"> |
16 | <item> |
17 | <layout class="QVBoxLayout" name="verticalLayout_2"> |
18 | |
19 | === modified file 'ubuntuone/controlpanel/cache.py' |
20 | --- ubuntuone/controlpanel/cache.py 2011-09-02 17:59:39 +0000 |
21 | +++ ubuntuone/controlpanel/cache.py 2011-09-08 01:17:23 +0000 |
22 | @@ -33,13 +33,18 @@ |
23 | if self.logger is not None: |
24 | self.logger.debug('%s: started.', self.__class__.__name__) |
25 | |
26 | - @property |
27 | - def backend(self): |
28 | + def get_backend(self): |
29 | """A cached ControlBackend instance.""" |
30 | if not self._shared_objects: |
31 | self._shared_objects['backend'] = backend.ControlBackend() |
32 | return self._shared_objects['backend'] |
33 | |
34 | + def set_backend(self, new_value): |
35 | + """Set a new ControlBackend instance.""" |
36 | + self._shared_objects['backend'] = new_value |
37 | + |
38 | + backend = property(fget=get_backend, fset=set_backend) |
39 | + |
40 | def clear(self): |
41 | """Clear all cached objects.""" |
42 | self._shared_objects = {} |
43 | |
44 | === modified file 'ubuntuone/controlpanel/gui/qt/__init__.py' |
45 | --- ubuntuone/controlpanel/gui/qt/__init__.py 2011-09-01 17:11:16 +0000 |
46 | +++ ubuntuone/controlpanel/gui/qt/__init__.py 2011-09-08 01:17:23 +0000 |
47 | @@ -18,7 +18,13 @@ |
48 | |
49 | """The Qt graphical interface for the control panel for Ubuntu One.""" |
50 | |
51 | +import collections |
52 | +import logging |
53 | + |
54 | +from functools import wraps |
55 | + |
56 | from PyQt4 import QtGui, QtCore |
57 | +from twisted.internet import defer |
58 | |
59 | |
60 | def uri_hook(uri): |
61 | @@ -53,3 +59,46 @@ |
62 | icon.icon_name = icon_name |
63 | |
64 | return icon |
65 | + |
66 | + |
67 | +def handle_errors(error_handler=None, logger=None): |
68 | + """Decorator to handle errors when calling a function. |
69 | + |
70 | + if 'error_handler' is not None, it will be yielded on if any error happens. |
71 | + |
72 | + """ |
73 | + if logger is None: |
74 | + logger = logging.getLogger() |
75 | + |
76 | + def middle(f): |
77 | + """Decorator to handle errors when calling 'f'.""" |
78 | + |
79 | + @defer.inlineCallbacks |
80 | + @wraps(f) |
81 | + def inner(*args, **kwargs): |
82 | + """Call 'f' passing 'args' and 'kwargs'. |
83 | + |
84 | + Catch any error and show a error message. |
85 | + |
86 | + """ |
87 | + try: |
88 | + res = yield f(*args, **kwargs) |
89 | + except Exception, e: # pylint: disable=W0703 |
90 | + logger.exception(f.__name__) |
91 | + else: |
92 | + defer.returnValue(res) |
93 | + |
94 | + # this code will only be executed if there was an exception |
95 | + if error_handler is not None: |
96 | + yield error_handler() |
97 | + |
98 | + if len(e.args) > 0 and isinstance(e.args[0], collections.Mapping): |
99 | + msgs = e.args[0].itervalues() |
100 | + else: |
101 | + msgs = [e.__class__.__name__] + map(repr, e.args) |
102 | + msg = '\n'.join(msgs) |
103 | + QtGui.QMessageBox.warning(None, '', msg, QtGui.QMessageBox.Close) |
104 | + |
105 | + return inner |
106 | + |
107 | + return middle |
108 | |
109 | === modified file 'ubuntuone/controlpanel/gui/qt/account.py' |
110 | --- ubuntuone/controlpanel/gui/qt/account.py 2011-08-31 17:18:56 +0000 |
111 | +++ ubuntuone/controlpanel/gui/qt/account.py 2011-09-08 01:17:23 +0000 |
112 | @@ -45,6 +45,7 @@ |
113 | self.ui.edit_profile_button.uri = EDIT_PROFILE_LINK |
114 | self.ui.edit_services_button.uri = EDIT_ACCOUNT_LINK |
115 | |
116 | + # pylint: disable=E0202 |
117 | @defer.inlineCallbacks |
118 | def load(self): |
119 | """Load info.""" |
120 | |
121 | === modified file 'ubuntuone/controlpanel/gui/qt/addfolder.py' |
122 | --- ubuntuone/controlpanel/gui/qt/addfolder.py 2011-09-02 17:59:39 +0000 |
123 | +++ ubuntuone/controlpanel/gui/qt/addfolder.py 2011-09-08 01:17:23 +0000 |
124 | @@ -28,6 +28,7 @@ |
125 | from ubuntuone.controlpanel import cache |
126 | from ubuntuone.controlpanel.logger import setup_logging |
127 | from ubuntuone.controlpanel.gui import FOLDER_INVALID_PATH |
128 | +from ubuntuone.controlpanel.gui.qt import handle_errors |
129 | |
130 | |
131 | logger = setup_logging('qt.addfolder') |
132 | @@ -50,6 +51,7 @@ |
133 | self.clicked.connect(self.on_clicked) |
134 | |
135 | @QtCore.pyqtSlot() |
136 | + @handle_errors(logger=logger) |
137 | @defer.inlineCallbacks |
138 | def on_clicked(self): |
139 | """The 'Sync another folder' button was clicked.""" |
140 | |
141 | === modified file 'ubuntuone/controlpanel/gui/qt/controlpanel.py' |
142 | --- ubuntuone/controlpanel/gui/qt/controlpanel.py 2011-09-01 17:11:16 +0000 |
143 | +++ ubuntuone/controlpanel/gui/qt/controlpanel.py 2011-09-08 01:17:23 +0000 |
144 | @@ -72,6 +72,7 @@ |
145 | self.ui.switcher.setCurrentWidget(self.ui.management) |
146 | self.is_processing = False |
147 | |
148 | + # pylint: disable=E0202 |
149 | @defer.inlineCallbacks |
150 | def load(self): |
151 | """Load info.""" |
152 | |
153 | === modified file 'ubuntuone/controlpanel/gui/qt/device.py' |
154 | --- ubuntuone/controlpanel/gui/qt/device.py 2011-09-02 17:59:39 +0000 |
155 | +++ ubuntuone/controlpanel/gui/qt/device.py 2011-09-08 01:17:23 +0000 |
156 | @@ -27,8 +27,13 @@ |
157 | ) |
158 | from ubuntuone.controlpanel import cache |
159 | from ubuntuone.controlpanel.gui import DEVICE_CONFIRM_REMOVE |
160 | -from ubuntuone.controlpanel.gui.qt import icon_from_name, pixmap_from_name |
161 | +from ubuntuone.controlpanel.gui.qt import ( |
162 | + handle_errors, |
163 | + icon_from_name, |
164 | + pixmap_from_name, |
165 | +) |
166 | from ubuntuone.controlpanel.gui.qt.ui import device_ui |
167 | +from ubuntuone.controlpanel.logger import setup_logging |
168 | |
169 | COMPUTER_ICON = "computer" |
170 | PHONE_ICON = "phone" |
171 | @@ -44,6 +49,9 @@ |
172 | YES = QtGui.QMessageBox.Yes |
173 | |
174 | |
175 | +logger = setup_logging('qt.device') |
176 | + |
177 | + |
178 | def icon_name_from_type(device_type): |
179 | """Get the icon name for the device.""" |
180 | icon_name = DEVICE_TYPE_TO_ICON_MAP.get(device_type, DEFAULT_ICON) |
181 | @@ -70,8 +78,9 @@ |
182 | pixmap = pixmap_from_name(icon_name) |
183 | self.ui.device_icon_label.setPixmap(pixmap) |
184 | |
185 | + @QtCore.pyqtSlot() |
186 | + @handle_errors(logger=logger) |
187 | @defer.inlineCallbacks |
188 | - @QtCore.pyqtSlot() |
189 | def on_remove_device_button_clicked(self): |
190 | """The user wants to remove this device.""" |
191 | msg = DEVICE_CONFIRM_REMOVE |
192 | |
193 | === modified file 'ubuntuone/controlpanel/gui/qt/devices.py' |
194 | --- ubuntuone/controlpanel/gui/qt/devices.py 2011-09-02 17:59:39 +0000 |
195 | +++ ubuntuone/controlpanel/gui/qt/devices.py 2011-09-08 01:17:23 +0000 |
196 | @@ -47,6 +47,7 @@ |
197 | super(DevicesPanel, self)._setup() |
198 | self.ui.manage_devices_button.uri = EDIT_DEVICES_LINK |
199 | |
200 | + # pylint: disable=E0202 |
201 | @defer.inlineCallbacks |
202 | def load(self): |
203 | """Load info.""" |
204 | |
205 | === modified file 'ubuntuone/controlpanel/gui/qt/folders.py' |
206 | --- ubuntuone/controlpanel/gui/qt/folders.py 2011-08-31 17:18:56 +0000 |
207 | +++ ubuntuone/controlpanel/gui/qt/folders.py 2011-09-08 01:17:23 +0000 |
208 | @@ -82,6 +82,7 @@ |
209 | icon = icon_from_name('external_icon_orange') |
210 | self.ui.share_publish_button.setIcon(icon) |
211 | |
212 | + # pylint: disable=E0202 |
213 | @defer.inlineCallbacks |
214 | def load(self): |
215 | """Load specific tab info.""" |
216 | |
217 | === modified file 'ubuntuone/controlpanel/gui/qt/preferences.py' |
218 | --- ubuntuone/controlpanel/gui/qt/preferences.py 2011-08-31 17:18:56 +0000 |
219 | +++ ubuntuone/controlpanel/gui/qt/preferences.py 2011-09-08 01:17:23 +0000 |
220 | @@ -64,6 +64,7 @@ |
221 | ui_class = preferences_ui |
222 | logger = logger |
223 | |
224 | + # pylint: disable=E0202 |
225 | @defer.inlineCallbacks |
226 | def load(self): |
227 | """Load info.""" |
228 | |
229 | === modified file 'ubuntuone/controlpanel/gui/qt/signin.py' |
230 | --- ubuntuone/controlpanel/gui/qt/signin.py 2011-08-31 17:18:56 +0000 |
231 | +++ ubuntuone/controlpanel/gui/qt/signin.py 2011-09-08 01:17:23 +0000 |
232 | @@ -19,12 +19,10 @@ |
233 | """The signin page.""" |
234 | |
235 | from PyQt4 import QtCore |
236 | - |
237 | from twisted.internet import defer |
238 | -from ubuntuone.platform.credentials import CredentialsError |
239 | |
240 | from ubuntuone.controlpanel.gui import RESET_PASSWORD_LINK |
241 | -from ubuntuone.controlpanel.gui.qt import icon_from_name |
242 | +from ubuntuone.controlpanel.gui.qt import icon_from_name, handle_errors |
243 | from ubuntuone.controlpanel.gui.qt.ubuntuonebin import UbuntuOneBin |
244 | from ubuntuone.controlpanel.gui.qt.ui import signin_ui |
245 | from ubuntuone.controlpanel.logger import setup_logging, log_call |
246 | @@ -45,7 +43,6 @@ |
247 | def _setup(self): |
248 | """Do some extra setupping for the UI.""" |
249 | super(SignInPanel, self)._setup() |
250 | - self.ui.warning_label.setText("") |
251 | |
252 | self.ui.forgot_password_button.uri = RESET_PASSWORD_LINK |
253 | icon = icon_from_name('external_icon_orange') |
254 | @@ -64,21 +61,23 @@ |
255 | self.ui.signin_button.style().unpolish(self.ui.signin_button) |
256 | self.ui.signin_button.style().polish(self.ui.signin_button) |
257 | |
258 | + # pylint: disable=E0202 |
259 | + @defer.inlineCallbacks |
260 | + def load(self): |
261 | + """Load specific tab info.""" |
262 | + yield self.backend.get_credentials() |
263 | + |
264 | + @QtCore.pyqtSlot() |
265 | + @handle_errors(logger=logger) |
266 | @log_call(logger.debug) |
267 | @defer.inlineCallbacks |
268 | - @QtCore.pyqtSlot() |
269 | def on_signin_button_clicked(self): |
270 | """The 'Sign in' button was clicked.""" |
271 | - self.ui.warning_label.setText("") |
272 | email = unicode(self.ui.email_entry.text()) |
273 | password = unicode(self.ui.password_entry.text()) |
274 | self.is_processing = True |
275 | try: |
276 | result = yield self.backend.login(email=email, password=password) |
277 | - except CredentialsError, e: |
278 | - logger.info('on_signin_button_clicked: %r', e) |
279 | - self.ui.warning_label.setText(e.args[0]['message']) |
280 | - else: |
281 | logger.info('Emitting credentialsFound for email %r.', email) |
282 | self.credentialsFound.emit(result) |
283 | finally: |
284 | |
285 | === modified file 'ubuntuone/controlpanel/gui/qt/tests/__init__.py' |
286 | --- ubuntuone/controlpanel/gui/qt/tests/__init__.py 2011-09-02 17:59:39 +0000 |
287 | +++ ubuntuone/controlpanel/gui/qt/tests/__init__.py 2011-09-08 01:17:23 +0000 |
288 | @@ -22,7 +22,7 @@ |
289 | import os |
290 | import urllib |
291 | |
292 | -from PyQt4 import QtCore |
293 | +from PyQt4 import QtGui, QtCore |
294 | from ubuntuone.devtools.handlers import MementoHandler |
295 | |
296 | from ubuntuone.controlpanel import backend, cache |
297 | @@ -126,9 +126,32 @@ |
298 | return TOKEN |
299 | |
300 | |
301 | -class FakedConfirmDialog(object): |
302 | +class CrashyBackendException(Exception): |
303 | + """A faked backend crash.""" |
304 | + |
305 | + |
306 | +class CrashyBackend(FakedControlPanelBackend): |
307 | + """A faked backend that crashes.""" |
308 | + |
309 | + def __init__(self, *args, **kwargs): |
310 | + super(CrashyBackend, self).__init__(*args, **kwargs) |
311 | + for i in self.exposed_methods + ['get_credentials']: |
312 | + setattr(self, i, self._fail(i)) |
313 | + |
314 | + def _fail(self, f): |
315 | + """Crash boom bang.""" |
316 | + |
317 | + def inner(*args, **kwargs): |
318 | + """Raise a custom exception.""" |
319 | + raise CrashyBackendException(f) |
320 | + |
321 | + return inner |
322 | + |
323 | + |
324 | +class FakedDialog(object): |
325 | """Fake a confirmation dialog.""" |
326 | |
327 | + Close = 0 |
328 | response = args = kwargs = None |
329 | |
330 | @classmethod |
331 | @@ -163,6 +186,7 @@ |
332 | innerclass_name = None |
333 | class_ui = None |
334 | kwargs = {} |
335 | + logger = None |
336 | |
337 | def setUp(self): |
338 | cache.Cache._shared_objects = {} |
339 | @@ -180,11 +204,25 @@ |
340 | if getattr(self.ui, 'backend', None) is not None: |
341 | self.addCleanup(self.ui.backend._called.clear) |
342 | |
343 | - if getattr(self.ui, 'logger', None) is not None: |
344 | + logger = self.logger if self.logger is not None else \ |
345 | + getattr(self.ui, 'logger', None) |
346 | + self.memento = None |
347 | + if logger is not None: |
348 | self.memento = MementoHandler() |
349 | self.memento.setLevel(logging.DEBUG) |
350 | - self.ui.logger.addHandler(self.memento) |
351 | - self.addCleanup(self.ui.logger.removeHandler, self.memento) |
352 | + logger.addHandler(self.memento) |
353 | + self.addCleanup(logger.removeHandler, self.memento) |
354 | + |
355 | + # default response if user does nothing |
356 | + FakedFileDialog.response = QtCore.QString('') |
357 | + FakedFileDialog.args = None |
358 | + FakedFileDialog.kwargs = None |
359 | + self.patch(QtGui, 'QFileDialog', FakedFileDialog) |
360 | + |
361 | + FakedDialog.response = None |
362 | + FakedDialog.args = None |
363 | + FakedDialog.kwargs = None |
364 | + self.patch(QtGui, 'QMessageBox', FakedDialog) |
365 | |
366 | def get_pixmap_data(self, pixmap): |
367 | """Get the raw data of a QPixmap.""" |
368 | |
369 | === modified file 'ubuntuone/controlpanel/gui/qt/tests/test_addfolder.py' |
370 | --- ubuntuone/controlpanel/gui/qt/tests/test_addfolder.py 2011-08-29 13:28:58 +0000 |
371 | +++ ubuntuone/controlpanel/gui/qt/tests/test_addfolder.py 2011-09-08 01:17:23 +0000 |
372 | @@ -29,7 +29,9 @@ |
373 | from ubuntuone.controlpanel.gui.qt import addfolder as gui |
374 | from ubuntuone.controlpanel.gui.qt.tests import ( |
375 | BaseTestCase, |
376 | - FakedConfirmDialog, |
377 | + CrashyBackend, |
378 | + CrashyBackendException, |
379 | + FakedDialog, |
380 | FakedFileDialog, |
381 | set_path_on_file_dialog, |
382 | ) |
383 | @@ -53,20 +55,6 @@ |
384 | os.environ['HOME'] = USER_HOME |
385 | self.addCleanup(lambda: os.environ.__setitem__('HOME', old_home)) |
386 | |
387 | - # default response if user does nothing |
388 | - FakedFileDialog.response = gui.QtCore.QString('') |
389 | - FakedFileDialog.args = None |
390 | - FakedFileDialog.kwargs = None |
391 | - self.patch(gui.QtGui, 'QFileDialog', FakedFileDialog) |
392 | - |
393 | - FakedConfirmDialog.response = None |
394 | - FakedConfirmDialog.args = None |
395 | - FakedConfirmDialog.kwargs = None |
396 | - self.patch(gui.QtGui, 'QMessageBox', FakedConfirmDialog) |
397 | - |
398 | - # reset backend state |
399 | - self.ui.backend._called.clear() |
400 | - |
401 | @defer.inlineCallbacks |
402 | def assert_does_nothing(self, method_call): |
403 | """Nothing happens. |
404 | @@ -103,8 +91,8 @@ |
405 | yield self.assert_does_nothing(self.ui.click) |
406 | |
407 | # the warning dialog was not opened |
408 | - self.assertEqual(FakedConfirmDialog.args, None) |
409 | - self.assertEqual(FakedConfirmDialog.kwargs, None) |
410 | + self.assertEqual(FakedDialog.args, None) |
411 | + self.assertEqual(FakedDialog.kwargs, None) |
412 | |
413 | @defer.inlineCallbacks |
414 | def test_opens_warning_if_folder_path_not_valid(self): |
415 | @@ -116,9 +104,9 @@ |
416 | |
417 | args = {'folder_path': folder_path, 'home_folder': USER_HOME} |
418 | msg = gui.FOLDER_INVALID_PATH % args |
419 | - self.assertEqual(FakedConfirmDialog.args, |
420 | + self.assertEqual(FakedDialog.args, |
421 | (self.ui, '', msg, gui.CLOSE)) |
422 | - self.assertEqual(FakedConfirmDialog.kwargs, {}) |
423 | + self.assertEqual(FakedDialog.kwargs, {}) |
424 | |
425 | yield self.assert_does_nothing(self.ui.click) |
426 | |
427 | @@ -129,8 +117,8 @@ |
428 | yield self.ui.click() |
429 | |
430 | # no warning |
431 | - self.assertEqual(FakedConfirmDialog.args, None) |
432 | - self.assertEqual(FakedConfirmDialog.kwargs, None) |
433 | + self.assertEqual(FakedDialog.args, None) |
434 | + self.assertEqual(FakedDialog.kwargs, None) |
435 | # backend called |
436 | self.assert_backend_called('create_folder', folder_path=folder) |
437 | |
438 | @@ -141,8 +129,8 @@ |
439 | yield self.ui.click() |
440 | |
441 | # no warning |
442 | - self.assertEqual(FakedConfirmDialog.args, None) |
443 | - self.assertEqual(FakedConfirmDialog.kwargs, None) |
444 | + self.assertEqual(FakedDialog.args, None) |
445 | + self.assertEqual(FakedDialog.kwargs, None) |
446 | # backend called |
447 | self.assert_backend_called('create_folder', folder_path=folder) |
448 | |
449 | @@ -170,3 +158,12 @@ |
450 | yield self.ui.click() |
451 | |
452 | self.assertEqual(self._called, ((), {})) |
453 | + |
454 | + @defer.inlineCallbacks |
455 | + def test_backend_error_is_handled(self): |
456 | + """Any error from the backend is properly handled.""" |
457 | + set_path_on_file_dialog() |
458 | + self.patch(self.ui, 'backend', CrashyBackend()) |
459 | + yield self.ui.click() |
460 | + |
461 | + self.assertTrue(self.memento.check_exception(CrashyBackendException)) |
462 | |
463 | === renamed file 'ubuntuone/controlpanel/gui/qt/tests/test_urihook.py' => 'ubuntuone/controlpanel/gui/qt/tests/test_common.py' |
464 | --- ubuntuone/controlpanel/gui/qt/tests/test_urihook.py 2011-09-01 17:11:16 +0000 |
465 | +++ ubuntuone/controlpanel/gui/qt/tests/test_common.py 2011-09-08 01:17:23 +0000 |
466 | @@ -18,12 +18,17 @@ |
467 | |
468 | """Tests for the uri_hook helper.""" |
469 | |
470 | +import logging |
471 | + |
472 | from PyQt4 import QtGui, QtCore |
473 | - |
474 | from twisted.internet import defer |
475 | |
476 | -from ubuntuone.controlpanel.gui.qt import uri_hook |
477 | -from ubuntuone.controlpanel.gui.qt.tests import BaseTestCase |
478 | +from ubuntuone.controlpanel.logger import setup_logging |
479 | +from ubuntuone.controlpanel.gui.qt import uri_hook, handle_errors |
480 | +from ubuntuone.controlpanel.gui.qt.tests import ( |
481 | + BaseTestCase, |
482 | + FakedDialog, |
483 | +) |
484 | |
485 | |
486 | class UriHookTestCase(BaseTestCase): |
487 | @@ -45,3 +50,161 @@ |
488 | expected = 'http://foo.bar/?next=https://one.ubuntu.com/' |
489 | uri_hook(expected) |
490 | self.assertEqual(self._called, ((QtCore.QUrl(expected),), {})) |
491 | + |
492 | + |
493 | +class HandleErrorTestCase(BaseTestCase): |
494 | + """Test suite for the generic error handler.""" |
495 | + |
496 | + error_handler = None |
497 | + use_logger = False |
498 | + logger = logging.getLogger() # root logger |
499 | + |
500 | + @defer.inlineCallbacks |
501 | + def setUp(self): |
502 | + yield super(HandleErrorTestCase, self).setUp() |
503 | + self.called = None |
504 | + self.result = None |
505 | + self.failure = None |
506 | + self.error_handler_called = None |
507 | + |
508 | + if self.use_logger: |
509 | + logger = self.logger |
510 | + else: |
511 | + logger = None |
512 | + |
513 | + self.decorate_me = handle_errors(error_handler=self.error_handler, |
514 | + logger=logger)(self._decorate_me) |
515 | + |
516 | + @defer.inlineCallbacks |
517 | + def _decorate_me(self, *args, **kwargs): |
518 | + """Helper to test thye decorator.""" |
519 | + if self.failure: |
520 | + # Raising only classes, instances or string are allowed |
521 | + # pylint: disable=E0702 |
522 | + raise self.failure |
523 | + |
524 | + yield |
525 | + self.called = (args, kwargs) |
526 | + defer.returnValue(self.result) |
527 | + |
528 | + @defer.inlineCallbacks |
529 | + def test_is_decorator(self): |
530 | + """Is a decorator.""" |
531 | + yield self.decorate_me() |
532 | + |
533 | + @defer.inlineCallbacks |
534 | + def test_params_are_passed(self): |
535 | + """Named and unnamed arguments are passed.""" |
536 | + args = ({}, object(), 'foo') |
537 | + kwargs = {'1': 1, 'test': None, '0': ['a']} |
538 | + yield self.decorate_me(*args, **kwargs) |
539 | + |
540 | + self.assertTrue(self.called, (args, kwargs)) |
541 | + |
542 | + @defer.inlineCallbacks |
543 | + def test_result_is_returned(self): |
544 | + """Result is returned.""" |
545 | + self.result = object() |
546 | + result = yield self.decorate_me() |
547 | + |
548 | + self.assertEqual(self.result, result) |
549 | + |
550 | + def test_name_is_preserved(self): |
551 | + """The method name is not masqueraded.""" |
552 | + self.assertEqual(self.decorate_me.__name__, self._decorate_me.__name__) |
553 | + |
554 | + @defer.inlineCallbacks |
555 | + def test_exeptions_are_handled(self): |
556 | + """Any exception is handled and logged in the root logger.""" |
557 | + msg = 'This is me failing badly.' |
558 | + self.failure = Exception(msg) |
559 | + |
560 | + yield self.decorate_me() |
561 | + |
562 | + logged = self.memento.check_exception(self.failure.__class__, msg) |
563 | + recs = '\n'.join(rec.exc_text for rec in self.memento.records) |
564 | + self.assertTrue(logged, 'Exception must be logged, got:\n%s' % recs) |
565 | + |
566 | + @defer.inlineCallbacks |
567 | + def test_show_error_message(self): |
568 | + """On error, show an error message.""" |
569 | + self.failure = Exception() |
570 | + |
571 | + yield self.decorate_me() |
572 | + |
573 | + msg = self.failure.__class__.__name__ |
574 | + self.assertEqual(FakedDialog.args, |
575 | + (None, '', msg, QtGui.QMessageBox.Close)) |
576 | + self.assertEqual(FakedDialog.kwargs, {}) |
577 | + |
578 | + @defer.inlineCallbacks |
579 | + def test_show_error_message_if_args(self): |
580 | + """On error, show an error message.""" |
581 | + msg1 = 'This is me failing badly.' |
582 | + msg2 = 'More details about what went wrong.' |
583 | + obj = object() |
584 | + self.failure = Exception(msg1, msg2, obj) |
585 | + |
586 | + yield self.decorate_me() |
587 | + |
588 | + msg = '\n'.join(map(repr, (msg1, msg2, obj))) |
589 | + msg = self.failure.__class__.__name__ + '\n' + msg |
590 | + self.assertEqual(FakedDialog.args, |
591 | + (None, '', msg, QtGui.QMessageBox.Close)) |
592 | + self.assertEqual(FakedDialog.kwargs, {}) |
593 | + |
594 | + @defer.inlineCallbacks |
595 | + def test_show_error_message_if_mapping(self): |
596 | + """On error, show an error message.""" |
597 | + msg1 = 'This is me failing badly.' |
598 | + msg2 = 'More details about what went wrong.' |
599 | + errdict = {'foo': msg1, 'bar': msg2} |
600 | + self.failure = Exception(errdict) |
601 | + |
602 | + yield self.decorate_me() |
603 | + |
604 | + msg = '\n'.join((msg1, msg2)) |
605 | + self.assertEqual(FakedDialog.args, |
606 | + (None, '', msg, QtGui.QMessageBox.Close)) |
607 | + self.assertEqual(FakedDialog.kwargs, {}) |
608 | + |
609 | + @defer.inlineCallbacks |
610 | + def test_no_error_message_if_no_exception(self): |
611 | + """On success, do not show an error message.""" |
612 | + yield self.decorate_me() |
613 | + |
614 | + self.assertEqual(FakedDialog.args, None) |
615 | + self.assertEqual(FakedDialog.kwargs, None) |
616 | + |
617 | + @defer.inlineCallbacks |
618 | + def test_call_error_handler(self): |
619 | + """On success, do not execute error_handler.""" |
620 | + yield self.decorate_me() |
621 | + self.assertFalse(self.error_handler_called) |
622 | + |
623 | + |
624 | +class HandleErrorWithCustomLoggerTestCase(HandleErrorTestCase): |
625 | + """Test suite for the generic error handler.""" |
626 | + |
627 | + use_logger = True |
628 | + logger = setup_logging('HandleErrorWithoutLoggerTestCase') # custom logger |
629 | + |
630 | + |
631 | +class HandleErrorWithHandlerTestCase(HandleErrorTestCase): |
632 | + """Test suite for the generic error handler when using a custom handler.""" |
633 | + |
634 | + @defer.inlineCallbacks |
635 | + def error_handler(self, *a, **kw): |
636 | + """Implement an error handler.""" |
637 | + self.error_handler_called = (a, kw) |
638 | + yield |
639 | + |
640 | + @defer.inlineCallbacks |
641 | + def test_call_error_handler(self): |
642 | + """On error, execute error_handler.""" |
643 | + msg = 'This is me failing badly.' |
644 | + self.failure = Exception(msg) |
645 | + |
646 | + yield self.decorate_me() |
647 | + |
648 | + self.assertEqual(self.error_handler_called, ((), {})) |
649 | |
650 | === modified file 'ubuntuone/controlpanel/gui/qt/tests/test_device.py' |
651 | --- ubuntuone/controlpanel/gui/qt/tests/test_device.py 2011-09-02 17:59:39 +0000 |
652 | +++ ubuntuone/controlpanel/gui/qt/tests/test_device.py 2011-09-08 01:17:23 +0000 |
653 | @@ -23,7 +23,9 @@ |
654 | from ubuntuone.controlpanel.gui.qt import device as gui |
655 | from ubuntuone.controlpanel.gui.qt.tests import ( |
656 | BaseTestCase, |
657 | - FakedConfirmDialog, |
658 | + CrashyBackend, |
659 | + CrashyBackendException, |
660 | + FakedDialog, |
661 | SAMPLE_COMPUTER_INFO, |
662 | SAMPLE_PHONE_INFO, |
663 | ) |
664 | @@ -41,6 +43,7 @@ |
665 | class_ui = gui.DeviceWidget |
666 | device_id = 'zaraza' |
667 | kwargs = {'device_id': device_id} |
668 | + logger = gui.logger |
669 | |
670 | def test_has_id(self): |
671 | """The device as an id, None by default.""" |
672 | @@ -98,10 +101,7 @@ |
673 | @defer.inlineCallbacks |
674 | def setUp(self): |
675 | yield super(RemoveDeviceTestCase, self).setUp() |
676 | - FakedConfirmDialog.response = gui.NO |
677 | - FakedConfirmDialog.args = None |
678 | - FakedConfirmDialog.kwargs = None |
679 | - self.patch(gui.QtGui, 'QMessageBox', FakedConfirmDialog) |
680 | + FakedDialog.response = gui.YES |
681 | |
682 | def test_remove_device_opens_confirmation_dialog(self): |
683 | """A confirmation dialog is opened when user clicks 'delete device'.""" |
684 | @@ -109,13 +109,13 @@ |
685 | |
686 | msg = gui.DEVICE_CONFIRM_REMOVE |
687 | buttons = gui.YES | gui.NO |
688 | - self.assertEqual(FakedConfirmDialog.args, |
689 | + self.assertEqual(FakedDialog.args, |
690 | (self.ui, '', msg, buttons, gui.NO)) |
691 | - self.assertEqual(FakedConfirmDialog.kwargs, {}) |
692 | + self.assertEqual(FakedDialog.kwargs, {}) |
693 | |
694 | def test_remove_device_does_not_remove_if_answer_is_no(self): |
695 | """The device is not removed is answer is No.""" |
696 | - FakedConfirmDialog.response = gui.NO |
697 | + FakedDialog.response = gui.NO |
698 | self.ui.removed.connect(self._set_called) |
699 | self.ui.ui.remove_device_button.click() |
700 | |
701 | @@ -124,7 +124,7 @@ |
702 | |
703 | def test_remove_device_does_remove_if_answer_is_yes(self): |
704 | """The device is removed is answer is Yes.""" |
705 | - FakedConfirmDialog.response = gui.YES |
706 | + FakedDialog.response = gui.YES |
707 | self.ui.ui.remove_device_button.click() |
708 | |
709 | self.assert_backend_called('remove_device', device_id=self.device_id) |
710 | @@ -138,7 +138,7 @@ |
711 | """Fire deferred when the device was removed.""" |
712 | d.callback(device_id) |
713 | |
714 | - FakedConfirmDialog.response = gui.YES |
715 | + FakedDialog.response = gui.YES |
716 | self.ui.removed.connect(self._set_called) |
717 | self.patch(self.ui.backend, 'remove_device', check) |
718 | self.ui.ui.remove_device_button.click() |
719 | @@ -148,8 +148,16 @@ |
720 | |
721 | def test_remove_device_emits_signal_when_not_removed(self): |
722 | """The signal 'removeCanceled' is emitted when user cancels removal.""" |
723 | - FakedConfirmDialog.response = gui.NO |
724 | + FakedDialog.response = gui.NO |
725 | self.ui.removeCanceled.connect(self._set_called) |
726 | self.ui.ui.remove_device_button.click() |
727 | |
728 | self.assertEqual(self._called, ((), {})) |
729 | + |
730 | + @defer.inlineCallbacks |
731 | + def test_backend_error_is_handled(self): |
732 | + """Any error from the backend is properly handled.""" |
733 | + self.patch(self.ui, 'backend', CrashyBackend()) |
734 | + yield self.ui.ui.remove_device_button.click() |
735 | + |
736 | + self.assertTrue(self.memento.check_exception(CrashyBackendException)) |
737 | |
738 | === modified file 'ubuntuone/controlpanel/gui/qt/tests/test_devices.py' |
739 | --- ubuntuone/controlpanel/gui/qt/tests/test_devices.py 2011-09-01 17:11:16 +0000 |
740 | +++ ubuntuone/controlpanel/gui/qt/tests/test_devices.py 2011-09-08 01:17:23 +0000 |
741 | @@ -22,7 +22,6 @@ |
742 | |
743 | from ubuntuone.controlpanel.gui.qt import devices as gui |
744 | from ubuntuone.controlpanel.gui.qt.tests import ( |
745 | - FakedConfirmDialog, |
746 | SAMPLE_DEVICES_INFO, |
747 | ) |
748 | from ubuntuone.controlpanel.gui.qt.tests.test_ubuntuonebin import ( |
749 | @@ -41,7 +40,6 @@ |
750 | def setUp(self): |
751 | yield super(DevicesPanelTestCase, self).setUp() |
752 | self.ui.backend.next_result = SAMPLE_DEVICES_INFO |
753 | - self.patch(gui.QtGui, 'QMessageBox', FakedConfirmDialog) |
754 | |
755 | def test_is_processing_while_asking_info(self): |
756 | """The ui is processing while the contents are loaded.""" |
757 | |
758 | === modified file 'ubuntuone/controlpanel/gui/qt/tests/test_folders.py' |
759 | --- ubuntuone/controlpanel/gui/qt/tests/test_folders.py 2011-09-01 17:11:16 +0000 |
760 | +++ ubuntuone/controlpanel/gui/qt/tests/test_folders.py 2011-09-08 01:17:23 +0000 |
761 | @@ -33,8 +33,7 @@ |
762 | ) |
763 | from ubuntuone.controlpanel.gui.qt import folders as gui |
764 | from ubuntuone.controlpanel.gui.qt.tests import ( |
765 | - FakedConfirmDialog, |
766 | - FakedFileDialog, |
767 | + FakedDialog, |
768 | ) |
769 | from ubuntuone.controlpanel.gui.qt.tests.test_ubuntuonebin import ( |
770 | UbuntuOneBinTestCase, |
771 | @@ -320,17 +319,6 @@ |
772 | # reset backend state |
773 | self.ui.backend._called.clear() |
774 | |
775 | - # default response if user does nothing |
776 | - FakedFileDialog.response = gui.QtCore.QString('') |
777 | - FakedFileDialog.args = None |
778 | - FakedFileDialog.kwargs = None |
779 | - self.patch(gui.QtGui, 'QFileDialog', FakedFileDialog) |
780 | - |
781 | - FakedConfirmDialog.response = None |
782 | - FakedConfirmDialog.args = None |
783 | - FakedConfirmDialog.kwargs = None |
784 | - self.patch(gui.QtGui, 'QMessageBox', FakedConfirmDialog) |
785 | - |
786 | def test_not_is_processing(self): |
787 | """Before clicking the add folder button, the UI is not processing.""" |
788 | self.assertFalse(self.ui.is_processing, 'ui must not be processing') |
789 | @@ -354,10 +342,7 @@ |
790 | def setUp(self): |
791 | yield super(FoldersPanelSubscriptionTestCase, self).setUp() |
792 | self.patch(gui.os.path, 'exists', lambda path: True) |
793 | - FakedConfirmDialog.response = gui.YES |
794 | - FakedConfirmDialog.args = None |
795 | - FakedConfirmDialog.kwargs = None |
796 | - self.patch(gui.QtGui, 'QMessageBox', FakedConfirmDialog) |
797 | + FakedDialog.response = gui.YES |
798 | |
799 | self.ui.process_info(FAKE_VOLUMES_MINIMAL_INFO) |
800 | # the music folder |
801 | @@ -436,9 +421,9 @@ |
802 | volume_path = self.item.volume_path |
803 | msg = gui.FOLDERS_CONFIRM_MERGE % {'folder_path': volume_path} |
804 | buttons = gui.YES | gui.NO | gui.CANCEL |
805 | - self.assertEqual(FakedConfirmDialog.args, |
806 | + self.assertEqual(FakedDialog.args, |
807 | (self.ui, '', msg, buttons, gui.YES)) |
808 | - self.assertEqual(FakedConfirmDialog.kwargs, {}) |
809 | + self.assertEqual(FakedDialog.kwargs, {}) |
810 | |
811 | @defer.inlineCallbacks |
812 | def test_confirm_dialog_if_path_does_not_exist(self): |
813 | @@ -452,14 +437,13 @@ |
814 | |
815 | yield self.ui.on_folders_itemChanged(self.item) |
816 | |
817 | - self.assertEqual(FakedConfirmDialog.args, None) |
818 | - self.assertEqual(FakedConfirmDialog.kwargs, None) |
819 | + self.assertEqual(FakedDialog.args, None) |
820 | + self.assertEqual(FakedDialog.kwargs, None) |
821 | |
822 | @defer.inlineCallbacks |
823 | def test_subscribe_does_not_call_backend_if_dialog_closed(self): |
824 | """Backend is not called if users closes the confirmation dialog.""" |
825 | - FakedConfirmDialog.response = gui.CANCEL |
826 | - self.patch(gui.QtGui, 'QMessageBox', FakedConfirmDialog) |
827 | + FakedDialog.response = gui.CANCEL |
828 | |
829 | # make sure the item is subscribed |
830 | self.ui.is_processing = True |
831 | @@ -468,7 +452,7 @@ |
832 | |
833 | yield self.ui.on_folders_itemChanged(self.item) |
834 | |
835 | - self.assertFalse(FakedConfirmDialog.args is None, 'warning was called') |
836 | + self.assertFalse(FakedDialog.args is None, 'warning was called') |
837 | self.assertNotIn('change_volume_settings', self.ui.backend._called) |
838 | self.assertFalse(self.ui.is_processing) |
839 | |
840 | @@ -478,8 +462,7 @@ |
841 | @defer.inlineCallbacks |
842 | def test_subscribe_does_not_call_backend_if_answer_is_no(self): |
843 | """Backend is not called if users clicks on 'No'.""" |
844 | - FakedConfirmDialog.response = gui.NO |
845 | - self.patch(gui.QtGui, 'QMessageBox', FakedConfirmDialog) |
846 | + FakedDialog.response = gui.NO |
847 | |
848 | # make sure the item is subscribed |
849 | self.ui.is_processing = True |
850 | @@ -488,7 +471,7 @@ |
851 | |
852 | yield self.ui.on_folders_itemChanged(self.item) |
853 | |
854 | - self.assertFalse(FakedConfirmDialog.args is None, 'warning was called') |
855 | + self.assertFalse(FakedDialog.args is None, 'warning was called') |
856 | self.assertNotIn('change_volume_settings', self.ui.backend._called) |
857 | self.assertFalse(self.ui.is_processing) |
858 | |
859 | @@ -504,10 +487,10 @@ |
860 | self.ui.is_processing = False |
861 | |
862 | # the confirm dialog was not called so far |
863 | - assert FakedConfirmDialog.args is None |
864 | - assert FakedConfirmDialog.kwargs is None |
865 | + assert FakedDialog.args is None |
866 | + assert FakedDialog.kwargs is None |
867 | |
868 | yield self.ui.on_folders_itemChanged(self.item) |
869 | |
870 | - self.assertTrue(FakedConfirmDialog.args is None, 'dialog was not run') |
871 | - self.assertTrue(FakedConfirmDialog.kwargs is None, 'dialog was hid') |
872 | + self.assertTrue(FakedDialog.args is None, 'dialog was not run') |
873 | + self.assertTrue(FakedDialog.kwargs is None, 'dialog was hid') |
874 | |
875 | === modified file 'ubuntuone/controlpanel/gui/qt/tests/test_signin.py' |
876 | --- ubuntuone/controlpanel/gui/qt/tests/test_signin.py 2011-08-31 17:18:56 +0000 |
877 | +++ ubuntuone/controlpanel/gui/qt/tests/test_signin.py 2011-09-08 01:17:23 +0000 |
878 | @@ -22,6 +22,10 @@ |
879 | |
880 | from ubuntuone.controlpanel.gui import qt |
881 | from ubuntuone.controlpanel.gui.qt import signin as gui |
882 | +from ubuntuone.controlpanel.gui.qt.tests import ( |
883 | + CrashyBackend, |
884 | + CrashyBackendException, |
885 | +) |
886 | from ubuntuone.controlpanel.gui.qt.tests.test_ubuntuonebin import ( |
887 | UbuntuOneBinTestCase, |
888 | ) |
889 | @@ -36,7 +40,7 @@ |
890 | |
891 | def fail(*a, **kw): |
892 | """Emit CredentialsError.""" |
893 | - raise gui.CredentialsError(MSG) |
894 | + raise TypeError(MSG) |
895 | |
896 | |
897 | class BaseSignInPanelTestCase(UbuntuOneBinTestCase): |
898 | @@ -81,10 +85,6 @@ |
899 | yield self.ui.ui.signin_button.click() |
900 | self.assertFalse(self.ui.is_processing) |
901 | |
902 | - def test_warning_label_empty(self): |
903 | - """The warning_label is empty at startup.""" |
904 | - self.assertEqual('', unicode(self.ui.ui.warning_label.text())) |
905 | - |
906 | def test_signin_disabled_at_startup(self): |
907 | """The signin_button is disabled at startup.""" |
908 | self.assertFalse(self.ui.ui.signin_button.isEnabled()) |
909 | @@ -139,31 +139,7 @@ |
910 | yield self.ui.ui.signin_button.click() |
911 | |
912 | self.assertEqual(self._called, ((TOKEN,), {})) |
913 | - self.assertEqual('', unicode(self.ui.ui.warning_label.text())) |
914 | - self.assertFalse(self.ui.is_processing) |
915 | - |
916 | - @defer.inlineCallbacks |
917 | - def test_signin_credentials_error(self): |
918 | - """Show error when CredentialsError was raised from the backend.""" |
919 | - self.patch(self.ui.backend, 'login', fail) |
920 | - self.ui.credentialsFound.connect(self._set_called) |
921 | - yield self.ui.ui.signin_button.click() |
922 | - |
923 | - self.assertFalse(self._called, 'credentialsFound must not be emitted.') |
924 | - self.assertEqual(MSG['message'], |
925 | - unicode(self.ui.ui.warning_label.text())) |
926 | - self.assertFalse(self.ui.is_processing) |
927 | - self.assertTrue(self.memento.check_info('signin_button_clicked', |
928 | - repr(MSG))) |
929 | - |
930 | - @defer.inlineCallbacks |
931 | - def test_signin_success_after_error(self): |
932 | - """Emit credentialsFound on signin success.""" |
933 | - self.patch(self.ui.backend, 'login', fail) # login failed |
934 | - yield self.ui.ui.signin_button.click() |
935 | - |
936 | - self.patch(self.ui.backend, 'login', lambda *a, **kw: TOKEN) # success |
937 | - yield self.test_signin_success() |
938 | + self.assertFalse(self.ui.is_processing) |
939 | |
940 | def test_signin_enabled_if_email_and_password(self): |
941 | """Enable signin_button if email and password are non empty.""" |
942 | @@ -182,3 +158,11 @@ |
943 | self.assertEqual(1, receivers) |
944 | |
945 | self._called = False |
946 | + |
947 | + @defer.inlineCallbacks |
948 | + def test_backend_error_is_handled(self): |
949 | + """Any error from the backend is properly handled.""" |
950 | + self.patch(self.ui, 'backend', CrashyBackend()) |
951 | + yield self.ui.ui.signin_button.click() |
952 | + |
953 | + self.assertTrue(self.memento.check_exception(CrashyBackendException)) |
954 | |
955 | === modified file 'ubuntuone/controlpanel/gui/qt/tests/test_ubuntuonebin.py' |
956 | --- ubuntuone/controlpanel/gui/qt/tests/test_ubuntuonebin.py 2011-09-02 17:59:39 +0000 |
957 | +++ ubuntuone/controlpanel/gui/qt/tests/test_ubuntuonebin.py 2011-09-08 01:17:23 +0000 |
958 | @@ -18,10 +18,15 @@ |
959 | |
960 | """Tests for the Ubuntu One Bin.""" |
961 | |
962 | +from twisted.internet import defer |
963 | from ubuntuone.devtools.testcase import skipIfOS |
964 | |
965 | from ubuntuone.controlpanel.gui.qt import ubuntuonebin as gui |
966 | -from ubuntuone.controlpanel.gui.qt.tests import BaseTestCase |
967 | +from ubuntuone.controlpanel.gui.qt.tests import ( |
968 | + BaseTestCase, |
969 | + CrashyBackend, |
970 | + CrashyBackendException, |
971 | +) |
972 | |
973 | # Attribute 'yyy' defined outside __init__, access to a protected member |
974 | # pylint: disable=W0201, W0212 |
975 | @@ -45,6 +50,7 @@ |
976 | def test_is_enabled_if_not_processing(self): |
977 | """If not processing, the UI is enabled.""" |
978 | self.ui.show() # need to show to test widgets visibility |
979 | + self.addCleanup(self.ui.hide) |
980 | |
981 | self.ui.is_processing = False |
982 | |
983 | @@ -55,8 +61,20 @@ |
984 | def test_is_not_enabled_if_processing(self): |
985 | """If processing, the UI is disabled.""" |
986 | self.ui.show() # need to show to test widgets visibility |
987 | + self.addCleanup(self.ui.hide) |
988 | |
989 | self.ui.is_processing = True |
990 | |
991 | self.assertFalse(self.ui.isEnabled()) |
992 | self.assertTrue(self.ui.overlay.isVisible()) |
993 | + |
994 | + @defer.inlineCallbacks |
995 | + def test_backend_error_is_handled(self): |
996 | + """Any error from the backend is properly handled.""" |
997 | + self.patch(self.ui, 'backend', CrashyBackend()) |
998 | + yield self.ui.load() |
999 | + |
1000 | + self.assertFalse(self.ui.is_processing) |
1001 | + if self.memento: |
1002 | + logged = self.memento.check_exception(CrashyBackendException) |
1003 | + self.assertTrue(logged) |
1004 | |
1005 | === modified file 'ubuntuone/controlpanel/gui/qt/ubuntuonebin.py' |
1006 | --- ubuntuone/controlpanel/gui/qt/ubuntuonebin.py 2011-09-02 17:59:39 +0000 |
1007 | +++ ubuntuone/controlpanel/gui/qt/ubuntuonebin.py 2011-09-08 01:17:23 +0000 |
1008 | @@ -21,6 +21,7 @@ |
1009 | from PyQt4 import QtGui |
1010 | |
1011 | from ubuntuone.controlpanel import cache |
1012 | +from ubuntuone.controlpanel.gui.qt import handle_errors |
1013 | from ubuntuone.controlpanel.gui.qt.loadingoverlay import LoadingOverlay |
1014 | |
1015 | |
1016 | @@ -45,6 +46,11 @@ |
1017 | self._is_processing = None |
1018 | self.is_processing = False |
1019 | |
1020 | + # pylint: disable=E0202 |
1021 | + handler = handle_errors(logger=self.logger, |
1022 | + error_handler=self._error_handler) |
1023 | + self.load = handler(self.load) |
1024 | + |
1025 | self._setup() |
1026 | |
1027 | def _get_is_processing(self): |
1028 | @@ -64,6 +70,10 @@ |
1029 | |
1030 | is_processing = property(fget=_get_is_processing, fset=_set_is_processing) |
1031 | |
1032 | + def _error_handler(self): |
1033 | + """Custom error handler, unset is_processing.""" |
1034 | + self.is_processing = False |
1035 | + |
1036 | def _setup(self): |
1037 | """Do some extra setupping for the UI.""" |
1038 | |
1039 | @@ -82,5 +92,6 @@ |
1040 | |
1041 | # pylint: enable=C0103 |
1042 | |
1043 | + # pylint: disable=E0202 |
1044 | def load(self): |
1045 | """Load the widget with specific info.""" |
1046 | |
1047 | === modified file 'ubuntuone/controlpanel/tests/test_cache.py' |
1048 | --- ubuntuone/controlpanel/tests/test_cache.py 2011-09-02 18:10:13 +0000 |
1049 | +++ ubuntuone/controlpanel/tests/test_cache.py 2011-09-08 01:17:23 +0000 |
1050 | @@ -39,6 +39,13 @@ |
1051 | """The backend instance is successfully created.""" |
1052 | self.assertIsInstance(self.obj.backend, cache.backend.ControlBackend) |
1053 | |
1054 | + def test_set_backend(self): |
1055 | + """The backend instance is successfully assigned.""" |
1056 | + expected = object() |
1057 | + assert self.obj.backend is not expected |
1058 | + self.obj.backend = expected |
1059 | + self.assertTrue(self.obj.backend is expected) |
1060 | + |
1061 | def test_backend_is_cached(self): |
1062 | """The backend instance is cached.""" |
1063 | obj2 = cache.Cache() |
+1