Merge lp:~alecu/ubuntuone-client/cleanup-emit-signals into lp:ubuntuone-client
- cleanup-emit-signals
- Merge into trunk
Proposed by
Alejandro J. Cura
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Manuel de la Peña | ||||
Approved revision: | 1125 | ||||
Merged at revision: | 1126 | ||||
Proposed branch: | lp:~alecu/ubuntuone-client/cleanup-emit-signals | ||||
Merge into: | lp:ubuntuone-client | ||||
Diff against target: |
471 lines (+168/-88) 3 files modified
tests/platform/windows/test_ipc.py (+99/-28) ubuntuone/platform/windows/ipc.py (+18/-20) ubuntuone/platform/windows/ipc_client.py (+51/-40) |
||||
To merge this branch: | bzr merge lp:~alecu/ubuntuone-client/cleanup-emit-signals | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Manuel de la Peña (community) | Approve | ||
Natalia Bidart (community) | Approve | ||
Review via email: mp+73733@code.launchpad.net |
Commit message
Connect each IPC client to the signals it should listen to. (LP: #839060)
Description of the change
Connect each IPC client to the signals it should listen to. (LP: #839060)
To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote : | # |
Looks good to me!
review:
Approve
- 1124. By Alejandro J. Cura
-
separate the list of signal handlers from the list of remote calls.
- 1125. By Alejandro J. Cura
-
fix some tests
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'tests/platform/windows/test_ipc.py' |
2 | --- tests/platform/windows/test_ipc.py 2011-08-26 00:03:30 +0000 |
3 | +++ tests/platform/windows/test_ipc.py 2011-09-02 15:03:18 +0000 |
4 | @@ -43,6 +43,7 @@ |
5 | IPCInterface, |
6 | IPCRoot, |
7 | PublicFiles, |
8 | + RemoteMeta, |
9 | Shares, |
10 | SignalBroadcaster, |
11 | Status, |
12 | @@ -58,9 +59,10 @@ |
13 | FoldersClient, |
14 | FileSystemClient, |
15 | PublicFilesClient, |
16 | + RemoteClient, |
17 | StatusClient, |
18 | SyncDaemonClient, |
19 | - SharesClient |
20 | + SharesClient, |
21 | ) |
22 | |
23 | TEST_PORT = 40404 |
24 | @@ -272,6 +274,7 @@ |
25 | """Return the public files remote object.""" |
26 | return self._public_files |
27 | |
28 | + |
29 | class TestSignalBroadcaster(MockerTestCase): |
30 | """Test the signal broadcaster code.""" |
31 | |
32 | @@ -279,13 +282,15 @@ |
33 | super(TestSignalBroadcaster, self).setUp() |
34 | self.client = self.mocker.mock() |
35 | self.broad_caster = SignalBroadcaster() |
36 | - self.broad_caster.clients.append(self.client) |
37 | |
38 | def test_remote_register_to_signals(self): |
39 | """Assert that the client was added.""" |
40 | self.mocker.replay() |
41 | - self.broad_caster.remote_register_to_signals(self.client) |
42 | - self.assertTrue(self.client in self.broad_caster.clients) |
43 | + signals = ["demo_signal1", "demo_signal2"] |
44 | + self.broad_caster.remote_register_to_signals(self.client, signals) |
45 | + for signal in signals: |
46 | + clients = self.broad_caster.clients_per_signal[signal] |
47 | + self.assertTrue(self.client in clients) |
48 | |
49 | def test_emit_signal(self): |
50 | """Assert that the client method was called.""" |
51 | @@ -299,6 +304,8 @@ |
52 | deferred.addErrback(ANY, ANY, ANY) |
53 | deferred.addErrback(ANY, ANY, ANY) |
54 | self.mocker.replay() |
55 | + signals = [signal_name] |
56 | + self.broad_caster.remote_register_to_signals(self.client, signals) |
57 | self.broad_caster.emit_signal(signal_name, first, second, word=word) |
58 | |
59 | def test_emit_signal_dead_reference(self): |
60 | @@ -310,10 +317,11 @@ |
61 | self.mocker.replay() |
62 | |
63 | sb = SignalBroadcaster() |
64 | - sb.remote_register_to_signals(fake_remote_client) |
65 | - self.assertIn(fake_remote_client, sb.clients) |
66 | + sb.remote_register_to_signals(fake_remote_client, [sample_signal]) |
67 | + self.assertIn(fake_remote_client, sb.clients_per_signal[sample_signal]) |
68 | sb.emit_signal(sample_signal) |
69 | - self.assertNotIn(fake_remote_client, sb.clients) |
70 | + self.assertNotIn(fake_remote_client, |
71 | + sb.clients_per_signal[sample_signal]) |
72 | |
73 | def test_emit_signal_some_dead_some_not(self): |
74 | """Test a clean reference after a dead one.""" |
75 | @@ -328,8 +336,8 @@ |
76 | self.mocker.replay() |
77 | |
78 | sb = SignalBroadcaster() |
79 | - sb.remote_register_to_signals(fake_dead_remote) |
80 | - sb.remote_register_to_signals(fake_alive_remote) |
81 | + sb.remote_register_to_signals(fake_dead_remote, [sample_signal]) |
82 | + sb.remote_register_to_signals(fake_alive_remote, [sample_signal]) |
83 | sb.emit_signal(sample_signal) |
84 | |
85 | |
86 | @@ -360,8 +368,10 @@ |
87 | fake_remote_client = FakeRemoteClient() |
88 | |
89 | sb = SignalBroadcaster() |
90 | - sb.remote_register_to_signals(fake_remote_client) |
91 | - self.assertIn(fake_remote_client, sb.clients) |
92 | + signals = [fake_remote_client.missing_signal] |
93 | + sb.remote_register_to_signals(fake_remote_client, signals) |
94 | + sb_clients = sb.clients_per_signal[fake_remote_client.missing_signal] |
95 | + self.assertIn(fake_remote_client, sb_clients) |
96 | sb.emit_signal(fake_remote_client.missing_signal) |
97 | |
98 | expected = ( |
99 | @@ -379,8 +389,10 @@ |
100 | fake_remote_client = FakeRemoteClient() |
101 | |
102 | sb = SignalBroadcaster() |
103 | - sb.remote_register_to_signals(fake_remote_client) |
104 | - self.assertIn(fake_remote_client, sb.clients) |
105 | + signals = [fake_remote_client.failing_signal] |
106 | + sb.remote_register_to_signals(fake_remote_client, signals) |
107 | + sb_clients = sb.clients_per_signal[fake_remote_client.failing_signal] |
108 | + self.assertIn(fake_remote_client, sb_clients) |
109 | sb.emit_signal(fake_remote_client.failing_signal) |
110 | |
111 | expected = ( |
112 | @@ -392,11 +404,38 @@ |
113 | self.assertIn(expected, warnings) |
114 | |
115 | |
116 | +class FakeRemoteObject(object): |
117 | + """A test helper.""" |
118 | + |
119 | + def __init__(self): |
120 | + """Initialize this test helper.""" |
121 | + self.called = [] |
122 | + |
123 | + def callRemote(self, *args): |
124 | + """A remote call to this object.""" |
125 | + self.called.append(args) |
126 | + |
127 | + |
128 | +class RemoteClientTestCase(TestCase): |
129 | + """Tests for the RemoteClient class.""" |
130 | + |
131 | + def test_register_to_signals(self): |
132 | + """Test the register_to_signals method.""" |
133 | + fake_remote_object = FakeRemoteObject() |
134 | + client = RemoteClient(fake_remote_object) |
135 | + client.signal_handlers = ["on_abc"] |
136 | + client.register_to_signals() |
137 | + expected = [ |
138 | + ("register_to_signals", client, client.signal_handlers) |
139 | + ] |
140 | + self.assertEqual(fake_remote_object.called, expected) |
141 | + |
142 | + |
143 | class TestStatusEmitSignals(PerspectiveBrokerTestCase, MockerTestCase): |
144 | """Tests that the emit methods are correct.""" |
145 | |
146 | def setUp(self): |
147 | - """Setup tests.""" |
148 | + """Setup tests.""" |
149 | super(TestStatusEmitSignals, self).setUp() |
150 | self.signal_method = self.mocker.mock() |
151 | self.status.syncdaemon_status = self.mocker.mock() |
152 | @@ -885,21 +924,11 @@ |
153 | root = yield self.client_factory.getRootObject() |
154 | remote = yield root.callRemote('get_status') |
155 | client = StatusClient(remote) |
156 | + # set the cb |
157 | + callback_names = [signal + "_cb" for signal in client.signal_handlers] |
158 | yield client.register_to_signals() |
159 | - # set the cb |
160 | - for signal in ['on_content_queue_changed_cb', |
161 | - 'on_invalid_name_cb', |
162 | - 'on_broken_node_cb', |
163 | - 'on_status_changed_cb', |
164 | - 'on_download_started_cb', |
165 | - 'on_download_file_progress_cb', |
166 | - 'on_download_finished_cb', |
167 | - 'on_upload_started_cb', |
168 | - 'on_upload_file_progress_cb', |
169 | - 'on_upload_finished_cb', |
170 | - 'on_account_changed_cb', |
171 | - 'on_metaqueue_changed_cb']: |
172 | - setattr(client, signal, self.mocker.mock()) |
173 | + for callback_name in callback_names: |
174 | + setattr(client, callback_name, self.mocker.mock()) |
175 | defer.returnValue(client) |
176 | |
177 | def test_current_status(self): |
178 | @@ -3119,6 +3148,48 @@ |
179 | self.sender.handle_default(event_name, **kwargs) |
180 | |
181 | |
182 | +class RemoteMetaTestCase(TestCase): |
183 | + """Tests for the RemoteMeta metaclass.""" |
184 | + |
185 | + def test_remote_calls_renamed(self): |
186 | + """The remote_calls are renamed.""" |
187 | + test_token = object() |
188 | + |
189 | + class TestClass(object): |
190 | + """A class for testing.""" |
191 | + |
192 | + __metaclass__ = RemoteMeta |
193 | + |
194 | + remote_calls = ['test_method'] |
195 | + |
196 | + def test_method(self): |
197 | + """Fake call.""" |
198 | + return test_token |
199 | + |
200 | + tc = TestClass() |
201 | + self.assertEquals(tc.test_method(), test_token) |
202 | + self.assertEquals(tc.remote_test_method(), test_token) |
203 | + |
204 | + def test_signal_handlers_renamed(self): |
205 | + """The signal_handlers are renamed.""" |
206 | + test_token = object() |
207 | + |
208 | + class TestClass(object): |
209 | + """A class for testing.""" |
210 | + |
211 | + __metaclass__ = RemoteMeta |
212 | + |
213 | + signal_handlers = ['test_signal_handler'] |
214 | + |
215 | + def test_signal_handler(self): |
216 | + """Fake call.""" |
217 | + return test_token |
218 | + |
219 | + tc = TestClass() |
220 | + self.assertEquals(tc.test_signal_handler(), test_token) |
221 | + self.assertEquals(tc.remote_test_signal_handler(), test_token) |
222 | + |
223 | + |
224 | class TestIPCRoot(BaseIPCTestCase): |
225 | """Ensure that the IPCRoot works as expected.""" |
226 | |
227 | |
228 | === modified file 'ubuntuone/platform/windows/ipc.py' |
229 | --- ubuntuone/platform/windows/ipc.py 2011-08-26 00:03:30 +0000 |
230 | +++ ubuntuone/platform/windows/ipc.py 2011-09-02 15:03:18 +0000 |
231 | @@ -20,6 +20,7 @@ |
232 | |
233 | import logging |
234 | |
235 | +from collections import defaultdict |
236 | from threading import Thread |
237 | |
238 | from twisted.internet import defer, reactor |
239 | @@ -139,10 +140,10 @@ |
240 | """ |
241 | |
242 | def __new__(cls, name, bases, attrs): |
243 | - remote_calls = attrs.get('remote_calls', None) |
244 | - if remote_calls: |
245 | - for current in remote_calls: |
246 | - attrs['remote_' + current] = attrs[current] |
247 | + remote_calls = attrs.get('remote_calls', []) |
248 | + signal_handlers = attrs.get('signal_handlers', []) |
249 | + for current in remote_calls + signal_handlers: |
250 | + attrs['remote_' + current] = attrs[current] |
251 | return super(RemoteMeta, cls).__new__(cls, name, bases, attrs) |
252 | |
253 | |
254 | @@ -154,7 +155,7 @@ |
255 | |
256 | def __init__(self): |
257 | """Create a new instance.""" |
258 | - self.clients = [] |
259 | + self.clients_per_signal = defaultdict(set) |
260 | |
261 | def _ignore_no_such_method(self, failure, signal_name, current_client): |
262 | """NoSuchMethod is not an error, ignore it.""" |
263 | @@ -167,33 +168,29 @@ |
264 | current_client, failure.value) |
265 | logger.warning('Traceback is:\n%s', failure.printDetailedTraceback()) |
266 | |
267 | - def remote_register_to_signals(self, client): |
268 | - """Allow a client to register to a signal.""" |
269 | - if client not in self.clients: |
270 | - self.clients.append(client) |
271 | - else: |
272 | - logger.warning('Client %s tried to register twice.', client) |
273 | + def remote_register_to_signals(self, client, signals): |
274 | + """Allow a client to register to some signals.""" |
275 | + for signal in signals: |
276 | + self.clients_per_signal[signal].add(client) |
277 | |
278 | def remote_unregister_to_signals(self, client): |
279 | - """Allow a client to register to a signal.""" |
280 | - if client in self.clients: |
281 | - self.clients.remove(client) |
282 | - else: |
283 | - logger.warning('Tried to remove %s when was not registered.', |
284 | - client) |
285 | + """Allow a client to unregister from the signal.""" |
286 | + for connected_clients in self.clients_per_signal.values(): |
287 | + if client in connected_clients: |
288 | + connected_clients.remove(client) |
289 | |
290 | def emit_signal(self, signal_name, *args, **kwargs): |
291 | """Emit the given signal to the clients.""" |
292 | logger.debug("emitting %r to all connected clients.", signal_name) |
293 | - dead_clients = [] |
294 | - for current_client in self.clients: |
295 | + dead_clients = set() |
296 | + for current_client in self.clients_per_signal[signal_name]: |
297 | try: |
298 | d = current_client.callRemote(signal_name, *args, **kwargs) |
299 | d.addErrback(self._ignore_no_such_method, signal_name, |
300 | current_client) |
301 | d.addErrback(self._other_failure, signal_name, current_client) |
302 | except DeadReferenceError: |
303 | - dead_clients.append(current_client) |
304 | + dead_clients.add(current_client) |
305 | for client in dead_clients: |
306 | self.remote_unregister_to_signals(client) |
307 | |
308 | @@ -379,6 +376,7 @@ |
309 | |
310 | def __init__(self, root, main, volume_manager, action_queue): |
311 | """ Creates the instance.""" |
312 | + super(SyncDaemon, self).__init__() |
313 | self.service = SyncdaemonService(root, main, volume_manager, |
314 | action_queue) |
315 | self.clients = [] |
316 | |
317 | === modified file 'ubuntuone/platform/windows/ipc_client.py' |
318 | --- ubuntuone/platform/windows/ipc_client.py 2011-07-15 17:03:13 +0000 |
319 | +++ ubuntuone/platform/windows/ipc_client.py 2011-09-02 15:03:18 +0000 |
320 | @@ -1,6 +1,7 @@ |
321 | # -*- coding: utf-8 -*- |
322 | # |
323 | -# Author: Manuel de la Pena<manuel@canonical.com> |
324 | +# Authors: Manuel de la Pena <manuel@canonical.com> |
325 | +# Alejandro J. Cura <alecu@canonical.com> |
326 | # |
327 | # Copyright 2011 Canonical Ltd. |
328 | # |
329 | @@ -65,13 +66,16 @@ |
330 | class RemoteClient(object): |
331 | """Represent a client for remote calls.""" |
332 | |
333 | + signal_handlers = [] |
334 | + |
335 | def __init__(self, remote_object): |
336 | """Create instance.""" |
337 | self.remote = remote_object |
338 | |
339 | def register_to_signals(self): |
340 | """Register to the signals.""" |
341 | - return self.remote.callRemote('register_to_signals', self) |
342 | + return self.remote.callRemote('register_to_signals', self, |
343 | + self.signal_handlers) |
344 | |
345 | def unregister_to_signals(self): |
346 | """Register to the signals.""" |
347 | @@ -124,18 +128,20 @@ |
348 | __metaclass__ = RemoteMeta |
349 | |
350 | # calls that will be accessible remotely |
351 | - remote_calls = ['on_content_queue_changed', |
352 | - 'on_invalid_name', |
353 | - 'on_broken_node', |
354 | - 'on_status_changed', |
355 | - 'on_download_started', |
356 | - 'on_download_file_progress', |
357 | - 'on_download_finished', |
358 | - 'on_upload_started', |
359 | - 'on_upload_file_progress', |
360 | - 'on_upload_finished', |
361 | - 'on_account_changed', |
362 | - 'on_metaqueue_changed'] |
363 | + signal_handlers = [ |
364 | + 'on_content_queue_changed', |
365 | + 'on_invalid_name', |
366 | + 'on_broken_node', |
367 | + 'on_status_changed', |
368 | + 'on_download_started', |
369 | + 'on_download_file_progress', |
370 | + 'on_download_finished', |
371 | + 'on_upload_started', |
372 | + 'on_upload_file_progress', |
373 | + 'on_upload_finished', |
374 | + 'on_account_changed', |
375 | + 'on_metaqueue_changed', |
376 | + ] |
377 | |
378 | def __init__(self, remote_status): |
379 | """Creates the instance.""" |
380 | @@ -228,7 +234,7 @@ |
381 | __metaclass__ = RemoteMeta |
382 | |
383 | # calls that will be accessible remotely |
384 | - remote_calls = ['on_event',] |
385 | + signal_handlers = ['on_event',] |
386 | |
387 | def __init__(self, remote_events): |
388 | """Creates the instance.""" |
389 | @@ -249,8 +255,7 @@ |
390 | __metaclass__ = RemoteMeta |
391 | |
392 | # calls that will be accessible remotely |
393 | - remote_calls = ['on_root_mismatch', |
394 | - 'on_quota_exceeded'] |
395 | + signal_handlers = ['on_root_mismatch', 'on_quota_exceeded'] |
396 | |
397 | def __init__(self, remote_daemon): |
398 | """Creates the instance.""" |
399 | @@ -332,17 +337,19 @@ |
400 | __metaclass__ = RemoteMeta |
401 | |
402 | # calls that will be accessible remotely |
403 | - remote_calls = ['on_share_deleted', |
404 | - 'on_share_changed', |
405 | - 'on_share_delete_error', |
406 | - 'on_share_created', |
407 | - 'on_share_create_error', |
408 | - 'on_share_answer_response', |
409 | - 'on_new_share', |
410 | - 'on_share_subscribed', |
411 | - 'on_share_subscribe_error', |
412 | - 'on_share_unsubscribed', |
413 | - 'on_share_unsubscribe_error'] |
414 | + signal_handlers = [ |
415 | + 'on_share_deleted', |
416 | + 'on_share_changed', |
417 | + 'on_share_delete_error', |
418 | + 'on_share_created', |
419 | + 'on_share_create_error', |
420 | + 'on_share_answer_response', |
421 | + 'on_new_share', |
422 | + 'on_share_subscribed', |
423 | + 'on_share_subscribe_error', |
424 | + 'on_share_unsubscribed', |
425 | + 'on_share_unsubscribe_error', |
426 | + ] |
427 | |
428 | def __init__(self, remote_shares): |
429 | """Create the instance.""" |
430 | @@ -556,14 +563,16 @@ |
431 | __metaclass__ = RemoteMeta |
432 | |
433 | # calls that will be accessible remotely |
434 | - remote_calls = ['on_folder_created', |
435 | - 'on_folder_create_error', |
436 | - 'on_folder_deleted', |
437 | - 'on_folder_delete_error', |
438 | - 'on_folder_subscribed', |
439 | - 'on_folder_subscribe_error', |
440 | - 'on_folder_unsubscribed', |
441 | - 'on_folder_unsubscribe_error'] |
442 | + signal_handlers = [ |
443 | + 'on_folder_created', |
444 | + 'on_folder_create_error', |
445 | + 'on_folder_deleted', |
446 | + 'on_folder_delete_error', |
447 | + 'on_folder_subscribed', |
448 | + 'on_folder_subscribe_error', |
449 | + 'on_folder_unsubscribed', |
450 | + 'on_folder_unsubscribe_error', |
451 | + ] |
452 | |
453 | def __init__(self, remote_folders): |
454 | """Creates the instance.""" |
455 | @@ -636,10 +645,12 @@ |
456 | __metaclass__ = RemoteMeta |
457 | |
458 | # calls that will be accessible remotely |
459 | - remote_calls = ['on_public_access_changed', |
460 | - 'on_public_access_change_error', |
461 | - 'on_public_files_list', |
462 | - 'on_public_files_list_error'] |
463 | + signal_handlers = [ |
464 | + 'on_public_access_changed', |
465 | + 'on_public_access_change_error', |
466 | + 'on_public_files_list', |
467 | + 'on_public_files_list_error', |
468 | + ] |
469 | |
470 | def __init__(self, remote_public_files): |
471 | super(PublicFilesClient, self).__init__(remote_public_files) |
A small comment, sending the entire collection of remote_calls to be signals seems wrong to me. Because remove_calls can be all the calls that can be remotly called from the client. I know that at this point we just have signals.. but who knows, what about using the @signal decorator to create a signals collection? Or at least a subset of all the remote_calls.