Merge lp:~cmiller/desktopcouch/defer-dbus-until-after-plugins into lp:desktopcouch
- defer-dbus-until-after-plugins
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Chad Miller |
Approved revision: | 281 |
Merged at revision: | 274 |
Proposed branch: | lp:~cmiller/desktopcouch/defer-dbus-until-after-plugins |
Merge into: | lp:desktopcouch |
Diff against target: |
718 lines (+243/-135) 10 files modified
desktopcouch/application/local_files.py (+3/-1) desktopcouch/application/platform/windows/tests/test_base_dirs.py (+2/-2) desktopcouch/application/plugins/__init__.py (+13/-3) desktopcouch/application/plugins/tests/test_plugins.py (+3/-1) desktopcouch/application/plugins/tests/test_ubuntuone_pairing.py (+33/-8) desktopcouch/application/plugins/ubuntuone_pairing.py (+62/-43) desktopcouch/application/service.py (+27/-4) desktopcouch/application/tests/test_service.py (+88/-62) desktopcouch/records/tests/test_mocked_server.py (+4/-3) desktopcouch/recordtypes/contacts/tests/test_view.py (+8/-8) |
To merge this branch: | bzr merge lp:~cmiller/desktopcouch/defer-dbus-until-after-plugins |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
dobey (community) | Approve | ||
Natalia Bidart (community) | Approve | ||
Review via email: mp+57589@code.launchpad.net |
Commit message
Add ability for plugins to delay DBus service activation until they are finished processing. Make ubuntuone_pairing plugin use this. (LP: #760236)
Description of the change
Natalia Bidart (nataliabidart) wrote : | # |
Also, a test run failed with:
=======
[FAIL]
Traceback (most recent call last):
File "/usr/lib/
testMethod()
File "/home/
self.
File "/usr/lib/
assertion_
File "/usr/lib/
raise self.failureExc
exceptions.
desktopcouch.
Natalia Bidart (nataliabidart) wrote : | # |
I can't reproduce the test failure in trunk, so it seems like something in this branch is generating that failure?
Natalia Bidart (nataliabidart) wrote : | # |
Chad,
I re ran the test suite several times and the test case will not fail anymore, looks like a transient error (that should be debugged but I don't want to delay your branch anymore).
I'm approving so you can move on, but please fix all the (new) "Reimport" errors from pylint before landing.
Thanks!
Chad Miller (cmiller) wrote : | # |
I've run this test in a loop more than 1200 times, and can't reproduce it. We've seen something like it on trunk before, though.
dobey (dobey) : | # |
Chad Miller (cmiller) wrote : | # |
The attempt to merge lp:~cmiller/desktopcouch/defer-dbus-until-after-plugins into lp:desktopcouch failed. Below is the output from the failed tests.
Apache CouchDB has started, time to relax.
Browse your desktop CouchDB at file://
desktopcouch.
TestMigration
test_
test_
test_
test_
TestRegistration
test_
desktopcouch.
TestCouchdbIo
test_
test_
test_mkuri ... [OK]
test_
test_
test_
desktopcouch.
TestNetworkIO
test_
desktopcouch.
TestGnomeKeyring
test_
test_
test_
test_
test_
test_
test_
desktopcouch.
BaseDirsTestCase
test_
test_
test_
test_
desktopcouch.
TestKeyring
test_
test_
test_
test_
desktopcouch.
TestLoadPlugins
test_
desktopcouch.
TestUbuntoneP
test_
...
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
The attempt to merge lp:~cmiller/desktopcouch/defer-dbus-until-after-plugins into lp:desktopcouch failed. Below is the output from the failed tests.
Apache CouchDB has started, time to relax.
Browse your desktop CouchDB at file://
desktopcouch.
TestMigration
test_
test_
test_
test_
TestRegistration
test_
desktopcouch.
TestUbuntoneP
test_
test_
test_
test_
TestUbuntuOne
test_
test_
test_
desktopcouch.
TestLoadPlugins
test_
desktopcouch.
TestNetworkIO
test_
desktopcouch.
TestCouchdbIo
test_
test_
test_mkuri ... [OK]
test_
test_
test_
desktopcouch.
TestService
test_
test_
test_
File "/usr/lib/
testMethod()
File "/usr/lib/
result = test_method()
File "/home/
put_
exceptions.
[ERROR]
desktopcouch.
TestKeyringIn
test_with_auth ... [OK]
test_
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
The attempt to merge lp:~cmiller/desktopcouch/defer-dbus-until-after-plugins into lp:desktopcouch failed. Below is the output from the failed tests.
Apache CouchDB has started, time to relax.
Browse your desktop CouchDB at file://
desktopcouch.
TestMigration
test_
test_
test_
test_
TestRegistration
test_
desktopcouch.
TestUbuntoneP
test_
test_
test_
test_
TestUbuntuOne
test_
test_
test_
desktopcouch.
TestLoadPlugins
test_
desktopcouch.
TestNetworkIO
test_
desktopcouch.
TestCouchdbIo
test_
test_
test_mkuri ... [OK]
test_
test_
test_
desktopcouch.
TestService
test_
test_
test_
File "/usr/lib/
testMethod()
File "/usr/lib/
result = test_method()
File "/home/
put_
exceptions.
[ERROR]
desktopcouch.
TestKeyringIn
test_with_auth ... [OK]
test_
dobey (dobey) wrote : | # |
Something very weird is going on in the tests here. This change seems to fix it here:
=== modified file 'desktopcouch/
--- desktopcouch/
+++ desktopcouch/
@@ -136,11 +136,13 @@ class TestUbuntuOnePl
+ old_pair = uone.pair_
+ uone.pair_
def test_got_
"""Check that pairing is called for Ubuntu One."""
@@ -148,11 +150,13 @@ class TestUbuntuOnePl
+ old_pair = uone.pair_
+ uone.pair_
def test_listen_
"""Test that listening to credentails works."""
- 281. By Chad Miller
-
Save and restore the monkey-patched pair_with_ubuntuone function. I have no idea how this was working, but tests fail elsewhere.
Preview Diff
1 | === modified file 'desktopcouch/application/local_files.py' |
2 | --- desktopcouch/application/local_files.py 2011-04-08 20:31:22 +0000 |
3 | +++ desktopcouch/application/local_files.py 2011-04-15 21:47:33 +0000 |
4 | @@ -83,9 +83,11 @@ |
5 | if "-hashed-" in bookmark_file_contents: |
6 | raise ValueError("Basic-auth cred lost.") |
7 | # trial run, check sanity. |
8 | + # pylint: disable=W0106 |
9 | re.findall( |
10 | "<!-- !!([^!]+)!!([^!]+)!! -->", |
11 | bookmark_file_contents)[-1] |
12 | + # pylint: enable=W0106 |
13 | self._fill_from_file(self.file_name_used) |
14 | return |
15 | except (IOError, ValueError, IndexError): |
16 | @@ -196,7 +198,7 @@ |
17 | for d in (run_dir, db_dir, config_dir): |
18 | try: |
19 | os.makedirs(d, 0700) |
20 | - except OSError, ex: |
21 | + except OSError: |
22 | pass # Probably that it already exists. |
23 | try: |
24 | os.chmod(d, 0700) |
25 | |
26 | === modified file 'desktopcouch/application/platform/windows/tests/test_base_dirs.py' |
27 | --- desktopcouch/application/platform/windows/tests/test_base_dirs.py 2011-01-25 17:58:49 +0000 |
28 | +++ desktopcouch/application/platform/windows/tests/test_base_dirs.py 2011-04-15 21:47:33 +0000 |
29 | @@ -63,7 +63,7 @@ |
30 | self.mocker.result('hive') |
31 | self._winreg.OpenKey('hive', SHELL_FOLDERS_KEY) |
32 | self.mocker.result('key') |
33 | - self._winreg.QueryInfoKey('key')[1] |
34 | + self._winreg.QueryInfoKey('key')[1] # pylint: disable=W0106 |
35 | self.mocker.throw(Exception('Cannot get info.')) |
36 | self._winreg.CloseKey('hive') |
37 | self._winreg.CloseKey('key') |
38 | @@ -78,7 +78,7 @@ |
39 | self.mocker.result('hive') |
40 | self._winreg.OpenKey('hive', SHELL_FOLDERS_KEY) |
41 | self.mocker.result('key') |
42 | - self._winreg.QueryInfoKey('key')[1] |
43 | + self._winreg.QueryInfoKey('key')[1] # pylint: disable=W0106 |
44 | self.mocker.result(1) |
45 | self._winreg.EnumValue('key', 0) |
46 | self.mocker.result(('AppData', 'path', 1)) |
47 | |
48 | === modified file 'desktopcouch/application/plugins/__init__.py' |
49 | --- desktopcouch/application/plugins/__init__.py 2011-01-24 14:30:34 +0000 |
50 | +++ desktopcouch/application/plugins/__init__.py 2011-04-15 21:47:33 +0000 |
51 | @@ -19,8 +19,18 @@ |
52 | DESKTOPCOUCH_PLUGIN_PATHS = [os.path.join(os.path.dirname(__file__))] |
53 | |
54 | |
55 | -def load_plugins(couchdb_port): |
56 | - """Load the desktopcouch application plug-ins.""" |
57 | +def load_plugins(couchdb_port, blocking_semaphores, gobject): |
58 | + """Load the desktopcouch application plug-ins. |
59 | + |
60 | + The blocking_semaphores set is OPTIONALLY mutated by any plugin to signal |
61 | + that the service is not ready until a plugin has finished its asynchronous |
62 | + operations. Plugins may add a distinguishing object to the set, and it |
63 | + must remove what it adds when it is finished. |
64 | + |
65 | + couchdb -- the integer of the port number of the running couchdb |
66 | + blocking_semaphores -- the set() of semaphores, which we will mutate |
67 | + gobject -- the mainloop module. always 'gobject' except when testing. |
68 | + """ |
69 | plugin_names = set() |
70 | for path in DESKTOPCOUCH_PLUGIN_PATHS: |
71 | try: |
72 | @@ -37,6 +47,6 @@ |
73 | modpath = name.replace(os.path.sep, '.')[:-3] |
74 | try: |
75 | plugin = __import__(modpath, None, None, ['']) |
76 | - plugin.plugin_init(couchdb_port) |
77 | + plugin.plugin_init(couchdb_port, blocking_semaphores, gobject) |
78 | except (ImportError, AttributeError): |
79 | logging.warning('Failed to load plug-in: %s', modpath) |
80 | |
81 | === modified file 'desktopcouch/application/plugins/tests/test_plugins.py' |
82 | --- desktopcouch/application/plugins/tests/test_plugins.py 2011-01-26 22:32:59 +0000 |
83 | +++ desktopcouch/application/plugins/tests/test_plugins.py 2011-04-15 21:47:33 +0000 |
84 | @@ -28,6 +28,8 @@ |
85 | |
86 | def setUp(self): |
87 | self.couchdb_port = platform.find_port(ctx=test_context) |
88 | + self.blockers = set() |
89 | + self.gobject = None |
90 | |
91 | def test_load_plugins(self): |
92 | """Test that plug-ins are loaded correctly.""" |
93 | @@ -44,7 +46,7 @@ |
94 | self._imported = modname |
95 | old_import = __import__ |
96 | __builtins__['__import__'] = _fake_import |
97 | - plugins.load_plugins(self.couchdb_port) |
98 | + plugins.load_plugins(self.couchdb_port, self.blockers, self.gobject) |
99 | __builtins__['__import__'] = old_import |
100 | plugins.DESKTOPCOUCH_PLUGIN_PATHS = old_paths |
101 | self.assertEqual(self._imported, __name__) |
102 | |
103 | === modified file 'desktopcouch/application/plugins/tests/test_ubuntuone_pairing.py' |
104 | --- desktopcouch/application/plugins/tests/test_ubuntuone_pairing.py 2011-01-26 22:32:59 +0000 |
105 | +++ desktopcouch/application/plugins/tests/test_ubuntuone_pairing.py 2011-04-15 21:47:33 +0000 |
106 | @@ -38,9 +38,11 @@ |
107 | self.couchdb_port = self.mocker.mock() |
108 | self.put_static_paired_service = self.mocker.mock() |
109 | self.database_class = self.mocker.mock() |
110 | + self.blocking_semaphores = self.mocker.mock() |
111 | |
112 | def test_pair_with_ubuntuone_no_view(self): |
113 | """Test that when the view is not present it is indeed created.""" |
114 | + # plugin_init adds name to blocking semaphores, but we remove it. |
115 | self.couchdb.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD) |
116 | self.mocker.result(False) |
117 | # we are interested in the fact that the view is created |
118 | @@ -50,8 +52,11 @@ |
119 | self.couchdb.execute_view(U1_PAIR_RECORD, U1_PAIR_RECORD) |
120 | self.mocker.result([]) |
121 | self.put_static_paired_service(None, 'ubuntuone', ctx=None, uri=ANY) |
122 | + self.blocking_semaphores.discard(ANY) |
123 | + |
124 | self.mocker.replay() |
125 | - pair_with_ubuntuone(self.couchdb_port, self.couchdb, |
126 | + pair_with_ubuntuone(self.couchdb_port, self.blocking_semaphores, |
127 | + management_db=self.couchdb, |
128 | db_class=self.database_class, |
129 | put_service_fn=self.put_static_paired_service) |
130 | self.mocker.verify() |
131 | @@ -59,21 +64,27 @@ |
132 | def test_pair_with_ubuntuone_no_record(self): |
133 | """Ensure pairing is not done when there are no records .""" |
134 | # execute the steps when no records are returned by the view |
135 | + # plugin_init adds name to blocking semaphores, but we remove it. |
136 | self.couchdb.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD) |
137 | self.mocker.result(True) |
138 | self.couchdb.execute_view(U1_PAIR_RECORD, U1_PAIR_RECORD) |
139 | self.mocker.result([]) |
140 | self.put_static_paired_service(None, 'ubuntuone', ctx=None, uri=ANY) |
141 | + self.blocking_semaphores.discard(ANY) |
142 | + |
143 | self.mocker.replay() |
144 | - pair_with_ubuntuone(self.couchdb_port, self.couchdb, |
145 | + pair_with_ubuntuone(self.couchdb_port, self.blocking_semaphores, |
146 | + management_db=self.couchdb, |
147 | db_class=self.database_class, |
148 | put_service_fn=self.put_static_paired_service) |
149 | + |
150 | self.mocker.verify() |
151 | |
152 | def test_pair_with_ubuntuone_user_deleted_record(self): |
153 | """Ensure pairing is not done when the user explicitly removed it.""" |
154 | # create a mock object that will be the result from the view |
155 | row = self.mocker.mock() |
156 | + # plugin_init adds name to blocking semaphores, but we remove it. |
157 | # execute the steps to show that the user deleted the record |
158 | self.couchdb.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD) |
159 | self.mocker.result(True) |
160 | @@ -82,14 +93,18 @@ |
161 | # FIXME does this do anything? |
162 | _ = row.value |
163 | self.mocker.result(1) |
164 | + self.blocking_semaphores.discard(ANY) |
165 | + |
166 | self.mocker.replay() |
167 | - pair_with_ubuntuone(self.couchdb_port, self.couchdb) |
168 | + pair_with_ubuntuone(self.couchdb_port, self.blocking_semaphores, |
169 | + management_db=self.couchdb) |
170 | self.mocker.verify() |
171 | |
172 | def test_pair_with_ubuntuone_record_present(self): |
173 | """Ensure pairing is not done when the record is already present.""" |
174 | # create a mock object that will be the result from the view |
175 | row = self.mocker.mock() |
176 | + # plugin_init adds name to blocking semaphores, but we remove it. |
177 | # execute the steps to show that the user deleted the record |
178 | self.couchdb.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD) |
179 | self.mocker.result(True) |
180 | @@ -98,8 +113,11 @@ |
181 | # FIXME does this do anything? |
182 | _ = row.value |
183 | self.mocker.result(0) |
184 | + self.blocking_semaphores.discard(ANY) |
185 | + |
186 | self.mocker.replay() |
187 | - pair_with_ubuntuone(self.couchdb_port, self.couchdb) |
188 | + pair_with_ubuntuone(self.couchdb_port, self.blocking_semaphores, |
189 | + management_db=self.couchdb) |
190 | self.mocker.verify() |
191 | |
192 | |
193 | @@ -109,6 +127,7 @@ |
194 | def setUp(self): |
195 | super(TestUbuntuOnePlugin, self).setUp() |
196 | self.couchdb_port = self.mocker.mock() |
197 | + self.blocking_semaphores = self.mocker.mock() |
198 | self.called = False |
199 | |
200 | def test_got_new_credentials_other(self): |
201 | @@ -117,21 +136,27 @@ |
202 | """Fail if we get called.""" |
203 | self.called = True |
204 | |
205 | + saved_pair = uone.pair_with_ubuntuone |
206 | uone.pair_with_ubuntuone = fail_if_called |
207 | - uone.got_new_credentials(self.couchdb_port, 'Unknown App', {}) |
208 | + uone.got_new_credentials(self.couchdb_port, self.blocking_semaphores, |
209 | + 'Unknown App', {}) |
210 | self.assertFalse(self.called, 'pair_with_ubuntuone was not expected.') |
211 | self.mocker.replay() |
212 | + uone.pair_with_ubuntuone = saved_pair |
213 | |
214 | def test_got_new_credentials(self): |
215 | """Check that pairing is called for Ubuntu One.""" |
216 | - def pass_if_called(db=None): |
217 | + def pass_if_called(db=None, semaphores=None): |
218 | """Check that pair_with_ubuntuone was called.""" |
219 | self.called = True |
220 | |
221 | + saved_pair = uone.pair_with_ubuntuone |
222 | uone.pair_with_ubuntuone = pass_if_called |
223 | - uone.got_new_credentials(self.couchdb_port, uone.APP_NAME, {}) |
224 | + uone.got_new_credentials(self.couchdb_port, self.blocking_semaphores, |
225 | + uone.APP_NAME, {}) |
226 | self.assertTrue(self.called, 'pair_with_ubuntuone was not called.') |
227 | self.mocker.replay() |
228 | + uone.pair_with_ubuntuone = saved_pair |
229 | |
230 | def test_listen_to_dbus(self): |
231 | """Test that listening to credentails works.""" |
232 | @@ -161,4 +186,4 @@ |
233 | |
234 | self.mocker.replay() |
235 | |
236 | - uone.listen_to_dbus(self.couchdb_port) |
237 | + uone.listen_to_dbus(self.couchdb_port, self.blocking_semaphores) |
238 | |
239 | === modified file 'desktopcouch/application/plugins/ubuntuone_pairing.py' |
240 | --- desktopcouch/application/plugins/ubuntuone_pairing.py 2011-01-27 19:25:23 +0000 |
241 | +++ desktopcouch/application/plugins/ubuntuone_pairing.py 2011-04-15 21:47:33 +0000 |
242 | @@ -24,6 +24,7 @@ |
243 | from desktopcouch.application.server import DesktopDatabase |
244 | from ubuntuone.clientdefs import APP_NAME |
245 | |
246 | +PLUGIN_NAME = __name__ |
247 | U1_PAIR_RECORD = "ubuntu_one_pair_record" |
248 | MAP_JS = """function(doc) { |
249 | if (doc.service_name == "ubuntuone") { |
250 | @@ -33,48 +34,57 @@ |
251 | """ |
252 | |
253 | |
254 | -def pair_with_ubuntuone(couchdb_port, management_db=None, |
255 | +def pair_with_ubuntuone(couchdb_port, blocking_semaphores, |
256 | + management_db=None, |
257 | db_class=DesktopDatabase, |
258 | put_service_fn=put_static_paired_service): |
259 | """Adds a pairing record with ubuntu one when needed.""" |
260 | - # Use explicit uri so that we do not access dbus service. |
261 | - uri = "http://localhost:%s" % (couchdb_port,) |
262 | - if not management_db: |
263 | - management_db = db_class("management", uri=uri, create=True, ctx=None) |
264 | - # we indeed have credentials to add to the pairing records |
265 | - # but first we ensure that the required view is present |
266 | - if not management_db.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD): |
267 | - management_db.add_view( |
268 | - U1_PAIR_RECORD, MAP_JS, design_doc=U1_PAIR_RECORD) |
269 | - view_results = management_db.execute_view(U1_PAIR_RECORD, U1_PAIR_RECORD) |
270 | - pairing_found = False |
271 | - # Results should contain either one row or no rows |
272 | - # If there is one row, its value will be 0, meaning that there is |
273 | - # already an Ubuntu One pairing record, or 1, meaning that there |
274 | - # was an Ubuntu One pairing record but it has since been unpaired |
275 | - # Only create a new record if there is not one already. Specifically, |
276 | - # do not add the record if there is a deleted one, as this means |
277 | - # that the user explicitly unpaired it! |
278 | - for row in view_results: |
279 | - pairing_found = True |
280 | - if row.value == 1: |
281 | - logging.debug("Not adding desktopcouch pairing since the user " |
282 | - "has explicitly unpaired with Ubuntu One") |
283 | - else: |
284 | - logging.debug("Not adding desktopcouch pairing since we are " |
285 | - "already paired") |
286 | - if not pairing_found: |
287 | - put_service_fn(None, "ubuntuone", uri=uri, ctx=None) |
288 | - logging.debug("Pairing desktopcouch with Ubuntu One") |
289 | - |
290 | - |
291 | -def got_new_credentials(couchdb_port, app_name, credentials): |
292 | + try: |
293 | + # Use explicit uri so that we do not access dbus service. |
294 | + uri = "http://localhost:%s" % (couchdb_port,) |
295 | + if not management_db: |
296 | + management_db = db_class("management", uri=uri, create=True, |
297 | + ctx=None) |
298 | + # we indeed have credentials to add to the pairing records |
299 | + # but first we ensure that the required view is present |
300 | + if not management_db.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD): |
301 | + management_db.add_view( |
302 | + U1_PAIR_RECORD, MAP_JS, design_doc=U1_PAIR_RECORD) |
303 | + view_results = management_db.execute_view(U1_PAIR_RECORD, |
304 | + U1_PAIR_RECORD) |
305 | + pairing_found = False |
306 | + # Results should contain either one row or no rows |
307 | + # If there is one row, its value will be 0, meaning that there is |
308 | + # already an Ubuntu One pairing record, or 1, meaning that there |
309 | + # was an Ubuntu One pairing record but it has since been unpaired |
310 | + # Only create a new record if there is not one already. Specifically, |
311 | + # do not add the record if there is a deleted one, as this means |
312 | + # that the user explicitly unpaired it! |
313 | + for row in view_results: |
314 | + pairing_found = True |
315 | + if row.value == 1: |
316 | + logging.debug("Not adding desktopcouch pairing since the user " |
317 | + "has explicitly unpaired with Ubuntu One") |
318 | + else: |
319 | + logging.debug("Not adding desktopcouch pairing since we are " |
320 | + "already paired") |
321 | + if not pairing_found: |
322 | + put_service_fn(None, "ubuntuone", uri=uri, ctx=None) |
323 | + logging.debug("Pairing desktopcouch with Ubuntu One") |
324 | + |
325 | + finally: |
326 | + logging.info("removing semaphore for %s", PLUGIN_NAME) |
327 | + blocking_semaphores.discard(PLUGIN_NAME) |
328 | + |
329 | + |
330 | +def got_new_credentials(couchdb_port, blocking_semaphores, |
331 | + app_name, credentials): |
332 | """Pair with Ubuntu One when we get the new credentials.""" |
333 | if app_name == APP_NAME: |
334 | - pair_with_ubuntuone(couchdb_port) |
335 | - |
336 | - |
337 | -def listen_to_dbus(couchdb_port): |
338 | + pair_with_ubuntuone(couchdb_port, blocking_semaphores) |
339 | + |
340 | + |
341 | +def listen_to_dbus(couchdb_port, blocking_semaphores): |
342 | """Set up the signal handler on D-Bus for Ubuntu One pairing.""" |
343 | import dbus |
344 | bus = dbus.SessionBus() |
345 | @@ -82,7 +92,9 @@ |
346 | try: |
347 | import ubuntu_sso |
348 | |
349 | - receiver = lambda *args: got_new_credentials(couchdb_port, *args) |
350 | + receiver = lambda *args: \ |
351 | + got_new_credentials(couchdb_port, blocking_semaphores, |
352 | + *args) |
353 | |
354 | iface = ubuntu_sso.DBUS_CREDENTIALS_IFACE |
355 | bus.add_signal_receiver(handler_function=receiver, |
356 | @@ -96,13 +108,20 @@ |
357 | sso_backend.find_credentials(APP_NAME, {}) |
358 | except ImportError: |
359 | logging.info('Ubuntu SSO is not available.') |
360 | - |
361 | - |
362 | -def plugin_init(couchdb_port): |
363 | + blocking_semaphores.discard(PLUGIN_NAME) |
364 | + |
365 | + |
366 | +def plugin_init(couchdb_port, blocking_semaphores, gobject): |
367 | """Set up the signal handler for pairing with Ubuntu One.""" |
368 | logging.info('Loaded Ubuntu One extension for desktopcouch.') |
369 | if sys.platform == 'win32': |
370 | logging.warning('Windows support for Ubuntu One is not yet ready.') |
371 | else: |
372 | - import gobject |
373 | - gobject.idle_add(listen_to_dbus, couchdb_port) |
374 | + |
375 | + # Signal that we are critical for desktopcouch usage, and the server |
376 | + # must not begin until we are finished. We are responsible for |
377 | + # removing this item from the list. |
378 | + logging.info("adding %s to to blocking semaphore list", PLUGIN_NAME) |
379 | + blocking_semaphores.add(PLUGIN_NAME) |
380 | + |
381 | + gobject.idle_add(listen_to_dbus, couchdb_port, blocking_semaphores) |
382 | |
383 | === modified file 'desktopcouch/application/service.py' |
384 | --- desktopcouch/application/service.py 2011-01-31 21:49:22 +0000 |
385 | +++ desktopcouch/application/service.py 2011-04-15 21:47:33 +0000 |
386 | @@ -39,6 +39,7 @@ |
387 | import logging |
388 | import logging.handlers |
389 | import signal |
390 | +import gobject |
391 | |
392 | from desktopcouch.application import local_files |
393 | from desktopcouch.application import replication |
394 | @@ -84,7 +85,8 @@ |
395 | replication_actions=replication, |
396 | advertiser_factory=PortAdvertiser, set_logging=set_up_logging, |
397 | fork=os.fork, nice=os.nice, |
398 | - kill=os.kill, sleep=time.sleep): |
399 | + kill=os.kill, sleep=time.sleep, set_type=set, |
400 | + gobject_module=gobject): |
401 | self._mainloop = main_loop |
402 | self._pid_finder = pid_finder |
403 | self._port_finder = port_finder |
404 | @@ -97,6 +99,8 @@ |
405 | self._nice = nice |
406 | self._kill = kill |
407 | self._sleep = sleep |
408 | + self._set = set_type |
409 | + self._gobject = gobject_module |
410 | # pylint: enable=C0301 |
411 | |
412 | def _start_replicator_main(self, couchdb_port): |
413 | @@ -112,11 +116,30 @@ |
414 | replication.tear_down(*replication_runtime) |
415 | |
416 | def _start_server_main(self, couchdb_port): |
417 | - """Start server.""" |
418 | - self._advertiser_factory(self._mainloop.stop, self._ctx) |
419 | + """Start server answering DBus calls, and run plugins first.""" |
420 | + |
421 | + def if_all_semaphores_cleared(blocking_semaphores, |
422 | + func, *args, **kwargs): |
423 | + """Run a function if no semaphores exist, else try later.""" |
424 | + if blocking_semaphores: |
425 | + return True # Make idle call try us again. |
426 | + else: |
427 | + func(*args, **kwargs) |
428 | + return False # Handled! |
429 | + |
430 | + blocking_semaphores = self._set() |
431 | + load_plugins(couchdb_port, blocking_semaphores, self._gobject) |
432 | + |
433 | + # Answering queries on DBus signals that we are ready for users |
434 | + # to connect. We mustn't begin that until every plugin has a chance |
435 | + # to run to completion if it needs it. |
436 | + self._gobject.idle_add(if_all_semaphores_cleared, blocking_semaphores, |
437 | + self._advertiser_factory, |
438 | + self._mainloop.stop, |
439 | + self._ctx) |
440 | + |
441 | logging.debug("starting dbus main loop") |
442 | try: |
443 | - load_plugins(couchdb_port) |
444 | self._mainloop.run() |
445 | finally: |
446 | logging.debug("ending dbus main loop") |
447 | |
448 | === modified file 'desktopcouch/application/tests/test_service.py' |
449 | --- desktopcouch/application/tests/test_service.py 2011-01-27 19:25:23 +0000 |
450 | +++ desktopcouch/application/tests/test_service.py 2011-04-15 21:47:33 +0000 |
451 | @@ -37,6 +37,8 @@ |
452 | self._replication = self.mocker.mock() |
453 | self._advertiser = self.mocker.mock() |
454 | self._resources = self.mocker.mock() |
455 | + self._set = self.mocker.mock() |
456 | + self._gobject = self.mocker.mock() |
457 | self._service = DesktopcouchService(self._mainloop, |
458 | pid_finder=self._pid_finder, |
459 | port_finder=self._port_finder, |
460 | @@ -48,65 +50,89 @@ |
461 | fork=self._fork, |
462 | nice=self._nice, |
463 | kill=self._kill, |
464 | - sleep=self._sleep) |
465 | - |
466 | - def test_start_new_desktopcouch_no_extensions(self): |
467 | - """Test that desktopcouch is started. |
468 | - |
469 | - We test that when the pid cannot be found we ensure |
470 | - that the desktopcouch instance is started and that the |
471 | - start as the dbus service, |
472 | - """ |
473 | - self._pid_finder(start_if_not_running=False, ctx=self._ctx) |
474 | - self.mocker.result(None) |
475 | - self._pid_finder(start_if_not_running=True, ctx=self._ctx) |
476 | - self.mocker.result(self._pid_result) |
477 | - self._port_finder(pid=self._pid_result, ctx=self._ctx) |
478 | - self.mocker.result(self._port_result) |
479 | - self._fork() |
480 | - self.mocker.result(234) |
481 | - self._fork() |
482 | - self.mocker.result(234) |
483 | - # XXX: call this? |
484 | - self._mainloop.stop # pylint: disable=W0104 |
485 | - self.mocker.result(ANY) |
486 | - self._advertiser(ANY, self._ctx) |
487 | - self._mainloop.run() |
488 | - self._stop_couchdb(ctx=self._ctx) |
489 | - self._kill(234, signal.SIGTERM) |
490 | - self._sleep(1) |
491 | - self._kill(234, signal.SIGKILL) |
492 | - self.mocker.replay() |
493 | - self._service.start() |
494 | - |
495 | - def test_start_new_desktopcouch_extensions(self): |
496 | - """Test that desktopcouch is started. |
497 | - |
498 | - We test that when the pid cannot be found we ensure |
499 | - that the desktopcouch instance is started and that the |
500 | - start as the dbus service, |
501 | - """ |
502 | - self._pid_finder(start_if_not_running=False, ctx=self._ctx) |
503 | - self.mocker.result(None) |
504 | - self._pid_finder(start_if_not_running=True, ctx=self._ctx) |
505 | - self.mocker.result(self._pid_result) |
506 | - self._port_finder(pid=self._pid_result, ctx=self._ctx) |
507 | - self.mocker.result(self._port_result) |
508 | - self._fork() |
509 | - self.mocker.result(234) |
510 | - self._fork() |
511 | - self.mocker.result(234) |
512 | - # XXX: call this? |
513 | - self._mainloop.stop # pylint: disable=W0104 |
514 | - self.mocker.result(ANY) |
515 | - self._advertiser(ANY, self._ctx) |
516 | - self._mainloop.run() |
517 | - self._stop_couchdb(ctx=self._ctx) |
518 | - self._kill(234, signal.SIGTERM) |
519 | - self._sleep(1) |
520 | - self._kill(234, signal.SIGKILL) |
521 | - self.mocker.replay() |
522 | - self._service.start() |
523 | + sleep=self._sleep, |
524 | + set_type=self._set, |
525 | + gobject_module=self._gobject) |
526 | + |
527 | + self.gobject_idle_task_list = list() |
528 | + |
529 | + def test_start_new_desktopcouch_with_plugins(self): |
530 | + """Test that desktopcouch is started. |
531 | + |
532 | + We test that when the pid cannot be found we ensure |
533 | + that the desktopcouch instance is started and that the |
534 | + start as the dbus service, |
535 | + """ |
536 | + self._pid_finder(start_if_not_running=False, ctx=self._ctx) |
537 | + self.mocker.result(None) |
538 | + self._pid_finder(start_if_not_running=True, ctx=self._ctx) |
539 | + self.mocker.result(self._pid_result) |
540 | + self._port_finder(pid=self._pid_result, ctx=self._ctx) |
541 | + self.mocker.result(self._port_result) |
542 | + self._fork() |
543 | + self.mocker.result(234) # We are parent |
544 | + self._fork() |
545 | + self.mocker.result(234) # We are parent |
546 | + |
547 | + # plugins load |
548 | + semaphores = self.mocker.mock() |
549 | + self._set() |
550 | + self.mocker.result(semaphores) |
551 | + |
552 | + self._mainloop.stop # pylint: disable=W0104 |
553 | + self.mocker.result(ANY) |
554 | + |
555 | + self._gobject.idle_add(ANY, self._port_result, semaphores) |
556 | + self._gobject.idle_add(ANY, semaphores, self._advertiser, ANY, |
557 | + self._ctx) |
558 | + |
559 | + self._mainloop.run() |
560 | + |
561 | + # Tasks are added to the mainloop's idle queue. With that^ |
562 | + # mainloop.run, they'd be called. Simulate their call. |
563 | + |
564 | + # Idle loop to add plugins calls a plugin. ubuntuone_pairing |
565 | + semaphores.add(ANY) # A plugin asserts it must be completed. |
566 | + |
567 | + # Idle loop calls plugin for ubuntuone_pairing, which fires up DBus |
568 | + # client to get credentials from ubuntu-login app. When that returns, |
569 | + # it calls got_new_credentials, which calls pair_with_ubuntuone. |
570 | + |
571 | + # Mock call to pair_with_ubuntuone |
572 | + management_db = self.mocker.mock() |
573 | + put_service_fn = self.mocker.mock() |
574 | + |
575 | + bool(management_db) |
576 | + self.mocker.result(True) |
577 | + |
578 | + management_db.view_exists(ANY, ANY) |
579 | + self.mocker.result(False) |
580 | + management_db.add_view(ANY, ANY, design_doc=ANY) |
581 | + |
582 | + row = self.mocker.mock() |
583 | + management_db.execute_view(ANY, ANY) |
584 | + self.mocker.result([row]) |
585 | + |
586 | + row.value # pylint: disable=W0104 |
587 | + self.mocker.result(1) |
588 | + |
589 | + semaphores.discard(ANY) |
590 | + |
591 | + # and then dbus service is ready to answer queries. |
592 | + |
593 | + self._stop_couchdb(ctx=self._ctx) |
594 | + self._kill(234, signal.SIGTERM) |
595 | + self._sleep(1) |
596 | + self._kill(234, signal.SIGKILL) |
597 | + |
598 | + self.mocker.replay() |
599 | + |
600 | + self._service.start() |
601 | + # Manually do what dbus idle-loop would do. |
602 | + import desktopcouch.application.plugins.ubuntuone_pairing as u_p |
603 | + u_p.pair_with_ubuntuone(self._port_result, semaphores, |
604 | + management_db=management_db, |
605 | + put_service_fn=put_service_fn) |
606 | |
607 | def test_start_desktopcouch_replication(self): |
608 | """ Test that the repliciation works. |
609 | @@ -119,7 +145,7 @@ |
610 | self._port_finder(pid=self._pid_result, ctx=self._ctx) |
611 | self.mocker.result(self._port_result) |
612 | self._fork() |
613 | - self.mocker.result(0) |
614 | + self.mocker.result(0) # We are child process |
615 | self._nice(10) |
616 | self._replication.set_up(ANY) |
617 | self._mainloop.run() |
618 | @@ -146,9 +172,9 @@ |
619 | self._port_finder(pid=self._pid_result, ctx=self._ctx) |
620 | self.mocker.result(self._port_result) |
621 | self._fork() |
622 | - self.mocker.result(567) |
623 | + self.mocker.result(567) # We are parent process |
624 | self._fork() |
625 | - self.mocker.result(0) |
626 | + self.mocker.result(0) # We are child process |
627 | self._sleep(ANY) |
628 | self._ctx.db_dir # searching for files # pylint: disable=W0104 |
629 | self.mocker.result("/tmp/migration-data/does/not/exist") |
630 | |
631 | === modified file 'desktopcouch/records/tests/test_mocked_server.py' |
632 | --- desktopcouch/records/tests/test_mocked_server.py 2011-04-04 18:54:26 +0000 |
633 | +++ desktopcouch/records/tests/test_mocked_server.py 2011-04-15 21:47:33 +0000 |
634 | @@ -61,6 +61,7 @@ |
635 | |
636 | |
637 | class TestMockedCouchDatabaseCreateStates(MockerTestCase): |
638 | + """Mocked Couch Database tests for create= flag states.""" |
639 | def setUp(self): |
640 | """Set up tests.""" |
641 | super(TestMockedCouchDatabaseCreateStates, self).setUp() |
642 | @@ -85,7 +86,7 @@ |
643 | self.mocker.result({'update_seq': []}) |
644 | |
645 | self.mocker.replay() |
646 | - database = DesktopDatabase(self.dbname, uri=self.uri, |
647 | + DesktopDatabase(self.dbname, uri=self.uri, |
648 | record_factory=self.record_factory, create=True, |
649 | server_class=self.server_class, |
650 | oauth_tokens=self.oauth_tokens, ctx=self.ctx) |
651 | @@ -105,7 +106,7 @@ |
652 | info["update_seq"] |
653 | self.mocker.result({}) |
654 | self.mocker.replay() |
655 | - database = DesktopDatabase(self.dbname, uri=self.uri, |
656 | + DesktopDatabase(self.dbname, uri=self.uri, |
657 | record_factory=self.record_factory, create=True, |
658 | server_class=self.server_class, |
659 | oauth_tokens=self.oauth_tokens, ctx=self.ctx) |
660 | @@ -140,7 +141,7 @@ |
661 | self.mocker.result({}) |
662 | |
663 | self.mocker.replay() |
664 | - database = DesktopDatabase(self.dbname, uri=self.uri, |
665 | + DesktopDatabase(self.dbname, uri=self.uri, |
666 | record_factory=self.record_factory, create=False, |
667 | server_class=self.server_class, |
668 | oauth_tokens=self.oauth_tokens, ctx=self.ctx) |
669 | |
670 | === modified file 'desktopcouch/recordtypes/contacts/tests/test_view.py' |
671 | --- desktopcouch/recordtypes/contacts/tests/test_view.py 2011-01-05 22:21:04 +0000 |
672 | +++ desktopcouch/recordtypes/contacts/tests/test_view.py 2011-04-15 21:47:33 +0000 |
673 | @@ -280,29 +280,29 @@ |
674 | |
675 | contacts = list( |
676 | view.find_contacts_starting(self.db, first_name="Frances")) |
677 | - self.assertEqual(len(contacts), 1) |
678 | + self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,)) |
679 | |
680 | contacts = list( |
681 | view.find_contacts_starting(self.db, birth_date="1918")) |
682 | - self.assertEqual(len(contacts), 1) |
683 | + self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,)) |
684 | |
685 | contacts = list( |
686 | view.find_contacts_starting(self.db, birth_date="1918-08")) |
687 | - self.assertEqual(len(contacts), 1) |
688 | + self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,)) |
689 | |
690 | contacts = list( |
691 | view.find_contacts_starting(self.db, wedding_date="1970")) |
692 | - self.assertEqual(len(contacts), 1) |
693 | + self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,)) |
694 | |
695 | contacts = list( |
696 | view.find_contacts_starting( |
697 | self.db, email_addressesaddress="blah.example.com")) |
698 | - self.assertEqual(len(contacts), 1) |
699 | + self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,)) |
700 | |
701 | contacts = list( |
702 | view.find_contacts_starting( |
703 | self.db, email_addressesaddress="berkeley")) |
704 | - self.assertEqual(len(contacts), 1) |
705 | + self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,)) |
706 | |
707 | contacts = list( |
708 | view.find_contacts_starting(self.db, first_name="random")) |
709 | @@ -317,7 +317,7 @@ |
710 | |
711 | contacts = list( |
712 | view.find_contacts_exact(self.db, first_name="Frances")) |
713 | - self.assertEqual(len(contacts), 1) |
714 | + self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,)) |
715 | |
716 | contacts = list(view.find_contacts_exact(self.db, birth_date="-08-23")) |
717 | - self.assertEqual(len(contacts), 1) |
718 | + self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,)) |
Can you please make the docstring of load_plugins pep-257 compliant?
Also, I don't understand why you're passing gobject around, is that really needed?