Merge lp:~facundo/magicicada-gui/simple-api into lp:magicicada-gui

Proposed by Facundo Batista
Status: Merged
Approved by: Natalia Bidart
Approved revision: 12
Merge reported by: Facundo Batista
Merged at revision: not available
Proposed branch: lp:~facundo/magicicada-gui/simple-api
Merge into: lp:magicicada-gui
Diff against target: 310 lines (+207/-28)
2 files modified
magicicada/syncdaemon.py (+52/-4)
magicicada/tests/test_syncdaemon.py (+155/-24)
To merge this branch: bzr merge lp:~facundo/magicicada-gui/simple-api
Reviewer Review Type Date Requested Status
Natalia Bidart Approve
Review via email: mp+25248@code.launchpad.net

Description of the change

Expose a simple API to the GUI.

These are the attributes (exposed in SyncDaemon.current_state):

  is_started
  is_connected
  is_online

And the methods:

  start
  stop
  connect
  disconnect

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Is there a typo here: """Tells the SyncDaemon that the user wants it to DISconnect.""" ?

When running nose, I get:

======================================================================
ERROR: Check that it polls mq until no more is needed.
----------------------------------------------------------------------
TimeoutError: <test_syncdaemon.MetaQueueChangedTests testMethod=test_mq_polling_untilfinish> (test_mq_polling_untilfinish) still running at 2.0 secs

======================================================================
ERROR: Check that it polls mq until no more is needed.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.6/dist-packages/twisted/internet/base.py", line 778, in runUntilCurrent
    call.func(*call.args, **call.kw)
  File "/home/nessita/projects/magicicada/review_simple-api/magicicada/tests/test_syncdaemon.py", line 365, in check
    self.assertEqual(len(calls), 3)
  File "/usr/lib/python2.6/dist-packages/twisted/trial/unittest.py", line 287, in failUnlessEqual
    % (msg, pformat(first), pformat(second)))
FailTest: not equal:
a = 2
b = 3

----------------------------------------------------------------------
Ran 70 tests in 3.033s

FAILED (errors=2)

review: Needs Fixing
9. By Facundo Batista

Merged trunk in

10. By Facundo Batista

Typo, and refactored test

11. By Facundo Batista

Not beyond 80 columns!

12. By Facundo Batista

Merged trunk in

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Nice!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'magicicada/syncdaemon.py'
2--- magicicada/syncdaemon.py 2010-05-17 16:10:28 +0000
3+++ magicicada/syncdaemon.py 2010-05-18 22:03:29 +0000
4@@ -29,12 +29,12 @@
5
6 from magicicada.helpers import NO_OP
7
8-
9 # structures that hold content and queue information
10 QueueData = collections.namedtuple('QueueData', 'operation path share node')
11 State = collections.namedtuple('State', 'name description is_error '
12 'is_connected is_online queues connection')
13
14+# regular expressions for parsing MetaQueue data
15 RE_OP_LISTDIR = re.compile("(ListDir)\(share_id=(.*?), node_id=(.*?), .*")
16 RE_OP_UNLINK = re.compile("(Unlink)\(share_id=(.*?), node_id=(.*?), .*")
17 RE_OP_MAKEFILE = re.compile(
18@@ -42,6 +42,35 @@
19 RE_OP_MAKEDIR = re.compile(
20 "(MakeDir)\(share_id=(.*?), parent_id=(.*?), name=(.*?), .*")
21
22+# structure that hold content and queue information
23+QueueData = collections.namedtuple('QueueData', 'operation path share node')
24+
25+class State(object):
26+ """Holds the state of SD."""
27+ _attrs = ['name', 'description', 'is_error', 'is_connected',
28+ 'is_online', 'queues', 'connection', 'is_started']
29+
30+ def __init__(self, syncdaemon):
31+ self._sd = syncdaemon
32+
33+ def __getattribute__(self, name):
34+ """Return the value if there."""
35+ if name[0] == "_":
36+ return object.__getattribute__(self, name)
37+ else:
38+ if name == 'is_started':
39+ v = self._sd._get_started()
40+ else:
41+ v = self.__dict__.get(name)
42+ return v
43+
44+ def _set(self, **data):
45+ """Sets the attributes fromd data, if allowed."""
46+ for name, value in data.iteritems():
47+ if name not in self._attrs:
48+ raise AttributeError("Name not in _attrs: %r" % name)
49+ self.__dict__[name] = value
50+
51
52 class SyncDaemon(object):
53 """Interface to Ubuntu One's SyncDaemon."""
54@@ -51,6 +80,7 @@
55 loop = DBusGMainLoop(set_as_default=True)
56 self._bus = bus = dbus.SessionBus(mainloop=loop)
57 self.sync_daemon_tool = tools.SyncDaemonTool(bus)
58+ self.current_state = State(self)
59
60 # hook up for signals and store info for the shutdown
61 _signals = [
62@@ -73,6 +103,11 @@
63 # calls to obtain data from SDT
64 self._get_content_queue = self.sync_daemon_tool.waiting_content
65 self._get_meta_queue = self.sync_daemon_tool.waiting_metadata
66+ self._get_started = tools.is_running
67+ self._do_start = self.sync_daemon_tool.start
68+ self._do_quit = self.sync_daemon_tool.quit
69+ self._do_connect = self.sync_daemon_tool.connect
70+ self._do_disconnect = self.sync_daemon_tool.disconnect
71
72 # previous data
73 self._last_CQ_data = None
74@@ -102,9 +137,7 @@
75 is_online = bool(state['is_online'])
76 queues = state['queues']
77 connection = state['connection']
78- self.current_state = State(name, description, is_error,
79- is_connected, is_online,
80- queues, connection)
81+ self.current_state._set(**state)
82 self.status_changed_callback(name, description, is_error,
83 is_connected, is_online,
84 queues, connection)
85@@ -198,3 +231,18 @@
86 d.addCallback(self._process_mq)
87 self._mqcaller = reactor.callLater(self._mq_poll_time, self._check_mq)
88
89+ def start(self):
90+ """Starts the SyncDaemon."""
91+ self._do_start()
92+
93+ def quit(self):
94+ """Stops the SyncDaemon and makes it quit."""
95+ self._do_quit()
96+
97+ def connect(self):
98+ """Tells the SyncDaemon that the user wants it to connect."""
99+ self._do_connect()
100+
101+ def disconnect(self):
102+ """Tells the SyncDaemon that the user wants it to disconnect."""
103+ self._do_disconnect()
104
105=== modified file 'magicicada/tests/test_syncdaemon.py'
106--- magicicada/tests/test_syncdaemon.py 2010-05-04 01:30:42 +0000
107+++ magicicada/tests/test_syncdaemon.py 2010-05-18 22:03:29 +0000
108@@ -18,12 +18,14 @@
109
110 """Tests for the SyncDaemon communication backend."""
111
112+import unittest
113+
114 import dbus
115 from dbus.mainloop.glib import DBusGMainLoop
116
117 # this should be imported *before* importing the reactor below, as needs to
118 # install the glib connection first
119-from magicicada.syncdaemon import SyncDaemon
120+from magicicada.syncdaemon import SyncDaemon, State
121
122 from dbus.lowlevel import SignalMessage
123 from twisted.trial.unittest import TestCase as TwistedTestCase
124@@ -340,34 +342,163 @@
125
126 def test_mq_polling_untilfinish(self):
127 """Check that it polls mq until no more is needed."""
128-
129- # set the callback, and adjust the polling time to .2 seconds
130+ # prepare different status changed signals
131+ d1 = dict(name='QUEUE_MANAGER', queues='WORKING_ON_BOTH',
132+ description='description', is_error='', is_connected='True',
133+ is_online='', connection='conn')
134+
135+ d2 = dict(name='QUEUE_MANAGER', queues='WORKING_ON_CONTENT',
136+ description='description', is_error='', is_connected='True',
137+ is_online='', connection='conn')
138+
139+ # set the callback, and adjust the polling time to faster
140 calls = []
141 def fake_get(*a):
142 calls.append(None)
143+ if len(calls) < 3:
144+ pass # no changes, should keep calling
145+ elif len(calls) == 3:
146+ self.send_signal(DBUS_IFACE_STATUS_NAME,
147+ 'StatusChanged', 'a{ss}', d2)
148+ # allow time to see if a mistaken call happens
149+ reactor.callLater(.5, deferred.callback, True)
150+ else:
151+ deferred.errback(ValueError("Too many calls"))
152 return defer.Deferred()
153+
154 self.sd._get_meta_queue = fake_get
155- self.sd._mq_poll_time = .4
156-
157- # prepare different status changed signals
158- d1 = dict(name='QUEUE_MANAGER', queues='WORKING_ON_BOTH',
159- description='description', is_error='', is_connected='True',
160- is_online='', connection='conn')
161-
162- d2 = dict(name='QUEUE_MANAGER', queues='WORKING_ON_CONTENT',
163- description='description', is_error='', is_connected='True',
164- is_online='', connection='conn')
165-
166- def check():
167- """Verify the polling function was called n times."""
168- self.assertEqual(len(calls), 3)
169- deferred.callback(True)
170-
171- # start with the working one, stop it .5 seconds later, and check
172+ self.sd._mq_poll_time = .1
173+
174 self.send_signal(DBUS_IFACE_STATUS_NAME, 'StatusChanged', 'a{ss}', d1)
175- reactor.callLater(1, self.send_signal, DBUS_IFACE_STATUS_NAME,
176- 'StatusChanged', 'a{ss}', d2)
177- reactor.callLater(1.2, check)
178-
179 deferred = defer.Deferred()
180 return deferred
181+
182+
183+class StateTests(unittest.TestCase):
184+ """Test State class."""
185+
186+ def test_initial(self):
187+ """Initial state for vals."""
188+ st = State(None)
189+ self.assertEqual(st.name, None)
190+
191+ def test_set_one_value(self):
192+ """Set one value."""
193+ st = State(None)
194+ st._set(name=55)
195+
196+ # check the one is set, the rest not
197+ self.assertEqual(st.name, 55)
198+ self.assertEqual(st.description, None)
199+
200+ def test_set_two_values(self):
201+ """Set two values."""
202+ st = State(None)
203+ st._set(name=55, description=77)
204+
205+ # check those two are set, the rest not
206+ self.assertEqual(st.name, 55)
207+ self.assertEqual(st.description, 77)
208+ self.assertEqual(st.is_error, None)
209+
210+ def test_bad_value(self):
211+ """Set a value that should not."""
212+ st = State(None)
213+ self.assertRaises(AttributeError, st._set, not_really_allowed=44)
214+
215+
216+class APITests(unittest.TestCase):
217+ """Check exposed methods and attributes."""
218+
219+ def setUp(self):
220+ """Set up the test."""
221+ self.sd = SyncDaemon()
222+
223+ self._replaced = None
224+ self.called = False
225+
226+ def tearDown(self):
227+ """Tear down the test."""
228+ if self._replaced is not None:
229+ setattr(*self._replaced)
230+ self.sd.shutdown()
231+
232+ def mpatch_called(self, obj, method_name):
233+ """Monkeypatch to flag called."""
234+ self._replaced = (obj, method_name, getattr(obj, method_name))
235+ f = lambda *a, **k: setattr(self, 'called', True)
236+ setattr(obj, method_name, f)
237+
238+ def test_defaults_are_callable(self):
239+ """Check the attributes are callable."""
240+ meths = (self.sd._get_content_queue, self.sd._get_meta_queue,
241+ self.sd._get_started, self.sd._do_start, self.sd._do_quit,
242+ self.sd._do_connect, self.sd._do_disconnect)
243+ for meth in meths:
244+ self.assertTrue(callable(meth), "Meth %r is not callable" % meth)
245+
246+ def test_is_connected_yes(self):
247+ """Check is_connected, True."""
248+ d = dict(name='name', description='description', is_error='',
249+ is_connected='True', is_online='', queues='queues',
250+ connection='connection')
251+ self.sd._on_status_changed(d)
252+ self.assertTrue(self.sd.current_state.is_connected)
253+
254+ def test_is_connected_no(self):
255+ """Check is_connected, False."""
256+ d = dict(name='name', description='description', is_error='',
257+ is_connected='', is_online='', queues='queues',
258+ connection='connection')
259+ self.sd._on_status_changed(d)
260+ self.assertFalse(self.sd.current_state.is_connected)
261+
262+ def test_is_online_yes(self):
263+ """Check is_online, True."""
264+ d = dict(name='name', description='description', is_error='',
265+ is_connected='True', is_online='True', queues='queues',
266+ connection='connection')
267+ self.sd._on_status_changed(d)
268+ self.assertTrue(self.sd.current_state.is_online)
269+
270+ def test_is_online_no(self):
271+ """Check is_online, False."""
272+ d = dict(name='name', description='description', is_error='',
273+ is_connected='True', is_online='', queues='queues',
274+ connection='connection')
275+ self.sd._on_status_changed(d)
276+ self.assertFalse(self.sd.current_state.is_online)
277+
278+ def test_is_started_yes(self):
279+ """Check is_started, True."""
280+ self.sd._get_started = lambda: True
281+ self.assertTrue(self.sd.current_state.is_started)
282+
283+ def test_is_started_no(self):
284+ """Check is_started, False."""
285+ self.sd._get_started = lambda: False
286+ self.assertFalse(self.sd.current_state.is_started)
287+
288+ def test_start(self):
289+ """Test start calls SD."""
290+ self.mpatch_called(self.sd, '_do_start')
291+ self.sd.start()
292+ self.assertTrue(self.called)
293+
294+ def test_quit(self):
295+ """Test quit calls SD."""
296+ self.mpatch_called(self.sd, '_do_quit')
297+ self.sd.quit()
298+ self.assertTrue(self.called)
299+
300+ def test_connect(self):
301+ """Test connect calls SD."""
302+ self.mpatch_called(self.sd, '_do_connect')
303+ self.sd.connect()
304+ self.assertTrue(self.called)
305+
306+ def test_disconnect(self):
307+ """Test disconnect calls SD."""
308+ self.mpatch_called(self.sd, '_do_disconnect')
309+ self.sd.disconnect()
310+ self.assertTrue(self.called)

Subscribers

People subscribed via source and target branches