Merge lp:~cmiller/desktopcouch/defer-dbus-until-after-plugins into lp:desktopcouch

Proposed by Chad Miller
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
Reviewer Review Type Date Requested Status
dobey (community) Approve
Natalia Bidart 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)

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

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?

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

Also, a test run failed with:

===============================================================================
[FAIL]
Traceback (most recent call last):
  File "/usr/lib/python2.7/unittest/case.py", line 321, in run
    testMethod()
  File "/home/nessita/canonical/desktopcouch/review_defer-dbus-until-after-plugins/desktopcouch/recordtypes/contacts/tests/test_view.py", line 287, in test_find_contact_starting
    self.assertEqual(len(contacts), 1)
  File "/usr/lib/python2.7/unittest/case.py", line 503, in assertEqual
    assertion_func(first, second, msg=msg)
  File "/usr/lib/python2.7/unittest/case.py", line 496, in _baseAssertEqual
    raise self.failureException(msg)
exceptions.AssertionError: 2 != 1

desktopcouch.recordtypes.contacts.tests.test_view.TestLocalFiles.test_find_contact_starting

review: Needs Fixing
Revision history for this message
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?

Revision history for this message
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!

review: Approve
Revision history for this message
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.

Revision history for this message
dobey (dobey) :
review: Approve
Revision history for this message
Chad Miller (cmiller) wrote :
Download full text (16.4 KiB)

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:///tmp/tmp_DEgO4/data/couchdb.html
desktopcouch.application.migration.tests.test_migration
  TestMigration
    test_migration_deleted_flag_to_trash ... [OK]
    test_migration_in_face_of_broken_records ... [OK]
    test_migration_script_is_run ... [OK]
    test_migration_script_is_run_and_can_access_view ... [OK]
  TestRegistration
    test_register_migration_is_added_to_the_registry ... [OK]
desktopcouch.application.pair.tests.test_couchdb_io
  TestCouchdbIo
    test_get_database_names_replicatable ... [OK]
    test_get_my_host_unique_id ... [OK]
    test_mkuri ... [OK]
    test_obsfuscation ... [OK]
    test_put_dynamic_paired_host ... [OK]
    test_put_static_paired_service ... [OK]
desktopcouch.application.pair.tests.test_network_io
  TestNetworkIO
    test_successful_lifespan ... [OK]
desktopcouch.application.platform.linux.tests.test_keyring
  TestGnomeKeyring
    test_get_aouth_no_keyring ... [OK]
    test_get_oauth_canceled_store ... [OK]
    test_get_oauth_data_stored ... [OK]
    test_get_user_name_password_keyring ... [OK]
    test_get_user_name_password_no_daemon ... [OK]
    test_get_user_name_password_not_in_keyring ... [OK]
    test_get_user_name_password_user_cancel ... [OK]
desktopcouch.application.platform.windows.tests.test_base_dirs
  BaseDirsTestCase
    test_get_special_folders ... [OK]
    test_get_special_folders_cannot_get_data ... [OK]
    test_get_special_folders_cannot_open_hive ... [OK]
    test_get_special_folders_cannot_open_key ... [OK]
desktopcouch.application.platform.windows.tests.test_keyring
  TestKeyring
    test_get_oauth_data ... [OK]
    test_get_user_name_password ... [OK]
    test_get_user_name_password_no_key ... [OK]
    test_get_user_name_password_no_value ... [OK]
desktopcouch.application.plugins.tests.test_plugins
  TestLoadPlugins
    test_load_plugins ... [OK]
desktopcouch.application.plugins.tests.test_ubuntuone_pairing
  TestUbuntonePairing
    test_pair_with_ubuntuone_no_record ... [OK]
...

Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (28.6 KiB)

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:///tmp/tmpJCnr4N/data/couchdb.html
desktopcouch.application.migration.tests.test_migration
  TestMigration
    test_migration_deleted_flag_to_trash ... [OK]
    test_migration_in_face_of_broken_records ... [OK]
    test_migration_script_is_run ... [OK]
    test_migration_script_is_run_and_can_access_view ... [OK]
  TestRegistration
    test_register_migration_is_added_to_the_registry ... [OK]
desktopcouch.application.plugins.tests.test_ubuntuone_pairing
  TestUbuntonePairing
    test_pair_with_ubuntuone_no_record ... [OK]
    test_pair_with_ubuntuone_no_view ... [OK]
    test_pair_with_ubuntuone_record_present ... [OK]
    test_pair_with_ubuntuone_user_deleted_record ... [OK]
  TestUbuntuOnePlugin
    test_got_new_credentials ... [OK]
    test_got_new_credentials_other ... [OK]
    test_listen_to_dbus ... [OK]
desktopcouch.application.plugins.tests.test_plugins
  TestLoadPlugins
    test_load_plugins ... [OK]
desktopcouch.application.pair.tests.test_network_io
  TestNetworkIO
    test_successful_lifespan ... [OK]
desktopcouch.application.pair.tests.test_couchdb_io
  TestCouchdbIo
    test_get_database_names_replicatable ... [OK]
    test_get_my_host_unique_id ... [OK]
    test_mkuri ... [OK]
    test_obsfuscation ... [OK]
    test_put_dynamic_paired_host ... [OK]
    test_put_static_paired_service ... [OK]
desktopcouch.application.tests.test_service
  TestService
    test_start_desktopcouch_replication ... [OK]
    test_start_migrate_data ... [OK]
    test_start_new_desktopcouch_with_plugins ... Traceback (most recent call last):
  File "/usr/lib/python2.7/unittest/case.py", line 321, in run
    testMethod()
  File "/usr/lib/pymodules/python2.7/mocker.py", line 146, in test_method_wrapper
    result = test_method()
  File "/home/otto/tarmac-builds/desktopcouch/trunk/desktopcouch/application/tests/test_service.py", line 135, in test_start_new_desktopcouch_with_plugins
    put_service_fn=put_service_fn)
exceptions.TypeError: fail_if_called() takes at most 1 argument (4 given)
[ERROR]
desktopcouch.application.tests.test_local_files
  TestKeyringIntegration
    test_with_auth ... [OK]
    test_with_no_...

Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (26.9 KiB)

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:///tmp/tmpHzJDQ2/data/couchdb.html
desktopcouch.application.migration.tests.test_migration
  TestMigration
    test_migration_deleted_flag_to_trash ... [OK]
    test_migration_in_face_of_broken_records ... [OK]
    test_migration_script_is_run ... [OK]
    test_migration_script_is_run_and_can_access_view ... [OK]
  TestRegistration
    test_register_migration_is_added_to_the_registry ... [OK]
desktopcouch.application.plugins.tests.test_ubuntuone_pairing
  TestUbuntonePairing
    test_pair_with_ubuntuone_no_record ... [OK]
    test_pair_with_ubuntuone_no_view ... [OK]
    test_pair_with_ubuntuone_record_present ... [OK]
    test_pair_with_ubuntuone_user_deleted_record ... [OK]
  TestUbuntuOnePlugin
    test_got_new_credentials ... [OK]
    test_got_new_credentials_other ... [OK]
    test_listen_to_dbus ... [OK]
desktopcouch.application.plugins.tests.test_plugins
  TestLoadPlugins
    test_load_plugins ... [OK]
desktopcouch.application.pair.tests.test_network_io
  TestNetworkIO
    test_successful_lifespan ... [OK]
desktopcouch.application.pair.tests.test_couchdb_io
  TestCouchdbIo
    test_get_database_names_replicatable ... [OK]
    test_get_my_host_unique_id ... [OK]
    test_mkuri ... [OK]
    test_obsfuscation ... [OK]
    test_put_dynamic_paired_host ... [OK]
    test_put_static_paired_service ... [OK]
desktopcouch.application.tests.test_service
  TestService
    test_start_desktopcouch_replication ... [OK]
    test_start_migrate_data ... [OK]
    test_start_new_desktopcouch_with_plugins ... Traceback (most recent call last):
  File "/usr/lib/python2.7/unittest/case.py", line 321, in run
    testMethod()
  File "/usr/lib/pymodules/python2.7/mocker.py", line 146, in test_method_wrapper
    result = test_method()
  File "/home/otto/tarmac-builds/desktopcouch/trunk/desktopcouch/application/tests/test_service.py", line 135, in test_start_new_desktopcouch_with_plugins
    put_service_fn=put_service_fn)
exceptions.TypeError: fail_if_called() takes at most 1 argument (4 given)
[ERROR]
desktopcouch.application.tests.test_local_files
  TestKeyringIntegration
    test_with_auth ... [OK]
    test_with_no_...

Revision history for this message
dobey (dobey) wrote :

Something very weird is going on in the tests here. This change seems to fix it here:

=== modified file 'desktopcouch/application/plugins/tests/test_ubuntuone_pairing.py'
--- desktopcouch/application/plugins/tests/test_ubuntuone_pairing.py 2011-04-13 21:31:15 +0000
+++ desktopcouch/application/plugins/tests/test_ubuntuone_pairing.py 2011-04-15 01:56:27 +0000
@@ -136,11 +136,13 @@ class TestUbuntuOnePlugin(MockerTestCase
             """Fail if we get called."""
             self.called = True

+ old_pair = uone.pair_with_ubuntuone
         uone.pair_with_ubuntuone = fail_if_called
         uone.got_new_credentials(self.couchdb_port, self.blocking_semaphores,
                                  'Unknown App', {})
         self.assertFalse(self.called, 'pair_with_ubuntuone was not expected.')
         self.mocker.replay()
+ uone.pair_with_ubuntuone = old_pair

     def test_got_new_credentials(self):
         """Check that pairing is called for Ubuntu One."""
@@ -148,11 +150,13 @@ class TestUbuntuOnePlugin(MockerTestCase
             """Check that pair_with_ubuntuone was called."""
             self.called = True

+ old_pair = uone.pair_with_ubuntuone
         uone.pair_with_ubuntuone = pass_if_called
         uone.got_new_credentials(self.couchdb_port, self.blocking_semaphores,
                                  uone.APP_NAME, {})
         self.assertTrue(self.called, 'pair_with_ubuntuone was not called.')
         self.mocker.replay()
+ uone.pair_with_ubuntuone = old_pair

     def test_listen_to_dbus(self):
         """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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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,))

Subscribers

People subscribed via source and target branches