Merge lp:~vds/desktopcouch/views-with-reconnector-proxy into lp:desktopcouch
- views-with-reconnector-proxy
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Manuel de la Peña | ||||
Approved revision: | 184 | ||||
Merged at revision: | 175 | ||||
Proposed branch: | lp:~vds/desktopcouch/views-with-reconnector-proxy | ||||
Merge into: | lp:desktopcouch | ||||
Diff against target: |
480 lines (+172/-21) 4 files modified
desktopcouch/records/server_base.py (+77/-15) desktopcouch/records/tests/test_server.py (+86/-4) desktopcouch/start_local_couchdb.py (+8/-1) desktopcouch/stop_local_couchdb.py (+1/-1) |
||||
To merge this branch: | bzr merge lp:~vds/desktopcouch/views-with-reconnector-proxy | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Manuel de la Peña (community) | Approve | ||
Eric Casteleijn (community) | Approve | ||
Review via email: mp+35853@code.launchpad.net |
Commit message
Now View and ViewResults can reconnect if the couchdb server crashes.
Description of the change
Now ViewResult reconnects when the server crashes.
Vincenzo Di Somma (vds) wrote : | # |
> Ooooh, live monkeys!
>
> Code looks good, but the tests take ages now, and it looks like the server is
> killed and restarted way too often, though most of the tests seem to pass.
> Adding test output below. If this is all intentional, approved.
>
> One test does fail, but I believe that's because I'm on lucid:
About the test failing, mocker is in Lucid and should also be a developer dependency, if you install the package the test will pass, I'll file a bug to update our dependency packages.
The couchdb server is killed and restarted many time on purpose, to check that desktopcouch is able to reconnect to a different instance of couchdb if the previous one crashed.
It slows down the tests a lot I know. But there's not much we can do here apart from moving that logic somewhere else, like in python-couchdb.
the weird text couchdb's fault, I'll try to hide it in the next branch.
Manuel de la Peña (mandel) wrote : | # |
All tests pass. I just have a question, dont we have a better name for a method than _is_bug_lp539674. I know we can go to lp and check that out, but it would be nice a more self explanatory name.
dobey (dobey) wrote : | # |
The attempt to merge lp:~vds/desktopcouch/views-with-reconnector-proxy into lp:desktopcouch failed. Below is the output from the failed tests.
running build
running build_py
creating build
creating build/lib.
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
dobey (dobey) wrote : | # |
The attempt to merge lp:~vds/desktopcouch/views-with-reconnector-proxy into lp:desktopcouch failed. Below is the output from the failed tests.
running build
running build_py
creating build
creating build/lib.
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
dobey (dobey) wrote : | # |
The attempt to merge lp:~vds/desktopcouch/views-with-reconnector-proxy into lp:desktopcouch failed. Below is the output from the failed tests.
running build
running build_py
creating build
creating build/lib.
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
creating build/lib.
copying desktopcouch/
copying desktopcouch/
copying desktopcouch/
- 183. By Vincenzo Di Somma
-
fixed issue with keyring
- 184. By Vincenzo Di Somma
-
logging when the issue happens
Preview Diff
1 | === modified file 'desktopcouch/records/server_base.py' |
2 | --- desktopcouch/records/server_base.py 2010-08-23 22:51:48 +0000 |
3 | +++ desktopcouch/records/server_base.py 2010-09-22 14:14:44 +0000 |
4 | @@ -150,22 +150,24 @@ |
5 | self._server_class_extras = server_class_extras |
6 | self.record_factory = record_factory or Record |
7 | self.server_uri = uri |
8 | + self._server = None |
9 | + self.db = None |
10 | self._reconnect() |
11 | self._changes_since = self.db.info()["update_seq"] |
12 | self._changes_last_used = 0 # Immediate run works. |
13 | |
14 | @staticmethod |
15 | - def _is_bug_lp539674(ex): |
16 | + def _is_reconnection_fail(ex): |
17 | return isinstance(ex, AttributeError) and \ |
18 | ex.args == ("'NoneType' object has no attribute 'makefile'",) |
19 | |
20 | def with_reconnects(self, func, *args, **kwargs): |
21 | - for retry in (2, 1, None): |
22 | + for retry in (3, 2, 1, None): |
23 | try: |
24 | return func(*args, **kwargs) |
25 | except Exception, e: |
26 | - if self._is_bug_lp539674(e) and retry: |
27 | - logging.warn("DB connection timed out. Reconnecting.") |
28 | + if self._is_reconnection_fail(e) and retry: |
29 | + logging.warn("DB connection failed. Reconnecting.") |
30 | self._reconnect() |
31 | continue |
32 | elif isinstance(e, socket.error): |
33 | @@ -175,9 +177,10 @@ |
34 | continue |
35 | else: |
36 | raise |
37 | + raise ValueError("failed to (re-)connect to couchdb server") |
38 | |
39 | def _reconnect(self, uri=None): |
40 | - logging.info("Connecting to %s." % (self.server_uri or "discovered local port",)) |
41 | + logging.info("Connecting to %s.", self.server_uri or "discovered local port") |
42 | |
43 | self._server = self._server_class(uri or self.server_uri, |
44 | **self._server_class_extras) |
45 | @@ -186,7 +189,12 @@ |
46 | self._server.create(self._database_name) |
47 | else: |
48 | raise NoSuchDatabase(self._database_name) |
49 | - self.db = self._server[self._database_name] |
50 | + if self.db is None: |
51 | + self.db = self._server[self._database_name] |
52 | + else: |
53 | + # Monkey-patch the object the user already uses. Oook! |
54 | + new_db = self._server[self._database_name] |
55 | + self.db.resource = new_db.resource |
56 | |
57 | def _temporary_query(self, map_fun, reduce_fun=None, language='javascript', |
58 | wrapper=None, **options): |
59 | @@ -381,9 +389,10 @@ |
60 | |
61 | def record_exists(self, record_id): |
62 | """Check if record with given id exists.""" |
63 | - if record_id not in self.db: |
64 | + try: |
65 | + record = self.with_reconnects(self.db.__getitem__, record_id) |
66 | + except ResourceNotFound: |
67 | return False |
68 | - record = self.with_reconnects(self.db.__getitem__, record_id) |
69 | return not row_is_deleted(record) |
70 | |
71 | def delete_view(self, view_name, design_doc=DEFAULT_DESIGN_DOCUMENT): |
72 | @@ -434,23 +443,28 @@ |
73 | **params): |
74 | """Execute view and return results.""" |
75 | |
76 | - class ReconnectingViewWrapper: |
77 | + class ReconnectingViewWrapper(object): |
78 | """A view from python-couchdb is an object with attributes that |
79 | cause HTTP requests to be fired off when accessed. If we wish |
80 | to be able to reconnect on disappearance of the server, then |
81 | we must intercept calls from user to python-couchdb.""" |
82 | |
83 | - def __init__(wrapper, view, *args, **kwargs): |
84 | - wrapper.view = self.with_reconnects(view, *args, **kwargs) |
85 | + def __init__(wrapper, obj, *args, **kwargs): |
86 | + wrapper.obj = self.with_reconnects(obj, *args, **kwargs) |
87 | |
88 | def ___call__(wrapper, **options): |
89 | - return self.with_reconnects(wrapper.view.__call__, **options) |
90 | + return self.with_reconnects(wrapper.obj.__call__, **options) |
91 | |
92 | def __iter__(wrapper): |
93 | - return self.with_reconnects(wrapper.view.__iter__) |
94 | + return self.view_with_reconnects(wrapper.obj.__iter__, |
95 | + wrapper.obj) |
96 | + |
97 | + def __len__(wrapper): |
98 | + return self.view_with_reconnects(wrapper.obj.__len__, |
99 | + wrapper.obj) |
100 | |
101 | def __getitem__(wrapper, key): |
102 | - return self.with_reconnects(wrapper.view.__getitem__, key) |
103 | + return ReconnectingViewWrapper(wrapper.obj.__getitem__, key) |
104 | |
105 | if design_doc is None: |
106 | design_doc = view_name |
107 | @@ -461,6 +475,44 @@ |
108 | **params) |
109 | return wrapper |
110 | |
111 | + def view_with_reconnects(self, func, view, *args, **kwargs): |
112 | + for retry in (3, 2, 1, None): |
113 | + try: |
114 | + return func(*args, **kwargs) |
115 | + except Exception, e: |
116 | + if self._is_reconnection_fail(e) and retry: |
117 | + logging.warn("DB connection failed. Reconnecting.") |
118 | + self._reconnect_view(view_result=view) |
119 | + continue |
120 | + elif isinstance(e, socket.error): |
121 | + logging.warn("Other socket error %s. Reconnecting.", e) |
122 | + sleep(0.3) |
123 | + self._reconnect() |
124 | + continue |
125 | + else: |
126 | + raise |
127 | + raise ValueError("failed to (re-)connect to couchdb server") |
128 | + |
129 | + def _reconnect_view(self, uri=None, view_result=None): |
130 | + logging.info("Connecting to %s.", |
131 | + self.server_uri or "discovered local port") |
132 | + self._server = self._server_class( |
133 | + uri or '/'.join(self.db.resource.uri.split('/')[:-1]), |
134 | + **self._server_class_extras) |
135 | + if self._database_name not in self._server: |
136 | + if self._create: |
137 | + self._server.create(self._database_name) |
138 | + else: |
139 | + raise NoSuchDatabase(self._database_name) |
140 | + if self.db is None: |
141 | + self.db = self._server[self._database_name] |
142 | + else: |
143 | + # Monkey-patch the object the user already uses. Oook! |
144 | + view_url = urlparse.urlparse(view_result.view.resource.uri) |
145 | + db_url = urlparse.urlparse(self.db.resource.uri) |
146 | + view_result.view.resource.uri = urlparse.urlunparse( |
147 | + [view_url[0], db_url[1]] + list(view_url[2:])) |
148 | + |
149 | def add_view(self, view_name, map_js, reduce_js, |
150 | design_doc=DEFAULT_DESIGN_DOCUMENT): |
151 | """Create a view, given a name and the two parts (map and reduce). |
152 | @@ -469,7 +521,7 @@ |
153 | design_doc = view_name |
154 | |
155 | view = ViewDefinition(design_doc, view_name, map_js, reduce_js) |
156 | - view.sync(self.db) |
157 | + self.with_reconnects(view.sync, self.db) |
158 | assert self.view_exists(view_name, design_doc) |
159 | |
160 | def view_exists(self, view_name, design_doc=DEFAULT_DESIGN_DOCUMENT): |
161 | @@ -595,6 +647,7 @@ |
162 | uri = couchdburi( |
163 | self._server.resource.uri, self.db.name, "_changes", |
164 | since=self._changes_since) |
165 | + ## Assume server has not crashed and URI is the same. FIXME |
166 | resp, data = self._server.resource.http.request(uri, "GET", "", {}) |
167 | if resp["status"] != '200': |
168 | raise IOError( |
169 | @@ -611,3 +664,12 @@ |
170 | # If exception in cb, we never update governor. |
171 | self._changes_last_used = now |
172 | return call_count |
173 | + |
174 | + def ensure_full_commit(self): |
175 | + """ |
176 | + Make sure that CouchDb flushes all writes to the database, |
177 | + flushing all delayed commits, before going on. |
178 | + """ |
179 | + self.db.resource.post( |
180 | + path='_ensure_full_commit', |
181 | + headers={'Content-Type': 'application/json'}) |
182 | |
183 | === modified file 'desktopcouch/records/tests/test_server.py' |
184 | --- desktopcouch/records/tests/test_server.py 2010-07-02 15:42:05 +0000 |
185 | +++ desktopcouch/records/tests/test_server.py 2010-09-22 14:14:44 +0000 |
186 | @@ -19,12 +19,17 @@ |
187 | |
188 | """testing database/contact.py module""" |
189 | import testtools |
190 | +import os |
191 | +import signal |
192 | +import time |
193 | |
194 | import desktopcouch.tests as test_environment |
195 | from desktopcouch.records.server import CouchDatabase |
196 | from desktopcouch.records.server_base import ( |
197 | row_is_deleted, NoSuchDatabase, FieldsConflict, ResourceConflict) |
198 | from desktopcouch.records.record import Record |
199 | +from desktopcouch.stop_local_couchdb import stop_couchdb |
200 | +from desktopcouch import find_pid |
201 | |
202 | # pylint can't deal with failing imports even when they're handled |
203 | # pylint: disable-msg=F0401 |
204 | @@ -53,7 +58,7 @@ |
205 | # Connect to CouchDB server |
206 | self.dbname = self._testMethodName |
207 | self.database = CouchDatabase(self.dbname, create=True, |
208 | - ctx=test_environment.test_context) |
209 | + ctx=self.get_test_context()) |
210 | #create some records to pull out and test |
211 | self.database.put_record(Record({ |
212 | "key1_1": "val1_1", "key1_2": "val1_2", "key1_3": "val1_3", |
213 | @@ -67,8 +72,28 @@ |
214 | |
215 | def tearDown(self): |
216 | """tear down each test""" |
217 | + this_context = self.get_test_context() |
218 | + if this_context != test_environment.test_context: |
219 | + stop_couchdb(ctx=this_context) |
220 | super(TestCouchDatabase, self).tearDown() |
221 | - del self.database._server[self.dbname] |
222 | + |
223 | + def get_test_context(self): |
224 | + return test_environment.test_context |
225 | + |
226 | + def maybe_die(self): |
227 | + pass |
228 | + |
229 | + def wait_until_server_dead(self, pid=None): |
230 | + if pid is not None: |
231 | + pid = find_pid(start_if_not_running=False, ctx=self.get_test_context()) |
232 | + if pid is None: |
233 | + return |
234 | + while True: |
235 | + try: |
236 | + os.kill(pid, 0) # Wait until exited |
237 | + time.sleep(0.1) |
238 | + except OSError: |
239 | + break |
240 | |
241 | def test_database_not_exists(self): |
242 | self.assertRaises( |
243 | @@ -78,12 +103,14 @@ |
244 | """Test getting mutliple records by type""" |
245 | records = self.database.get_records( |
246 | record_type="test.com",create_view=True) |
247 | - self.assertEqual(3,len(records)) |
248 | + self.maybe_die() # should be able to survive couchdb death |
249 | + self.assertEqual(3, len(records)) |
250 | |
251 | def test_get_record(self): |
252 | """Test getting a record.""" |
253 | record = Record({'record_number': 0}, record_type="http://example.com/") |
254 | record_id = self.database.put_record(record) |
255 | + self.maybe_die() # should be able to survive couchdb death |
256 | retrieved_record = self.database.get_record(record_id) |
257 | self.assertEqual(0, retrieved_record['record_number']) |
258 | |
259 | @@ -91,7 +118,8 @@ |
260 | """Test putting a record.""" |
261 | record = Record({'record_number': 0}, record_type="http://example.com/") |
262 | record_id = self.database.put_record(record) |
263 | - retrieved_record = self.database._server[self.dbname][record_id] |
264 | + self.maybe_die() # should be able to survive couchdb death |
265 | + retrieved_record = self.database.get_record(record_id) |
266 | self.assertEqual( |
267 | record['record_number'], retrieved_record['record_number']) |
268 | |
269 | @@ -121,7 +149,9 @@ |
270 | record = Record({'record_number': 0}, record_type="http://example.com/") |
271 | record_id = self.database.put_record(record) |
272 | self.database.delete_record(record_id) |
273 | + ###self.maybe_die() # should be able to survive couchdb death |
274 | deleted_record = self.database._server[self.dbname][record_id] |
275 | + #deleted_record = self.database.get_record(record_id) |
276 | self.assert_(deleted_record['application_annotations']['Ubuntu One'][ |
277 | 'private_application_annotations']['deleted']) |
278 | |
279 | @@ -154,6 +184,7 @@ |
280 | record = Record({'record_number': 0}, record_type="http://example.com/") |
281 | record_id = self.database.put_record(record) |
282 | self.database.delete_record(record_id) |
283 | + self.maybe_die() # should be able to survive couchdb death |
284 | retrieved_record = self.database.get_record(record_id) |
285 | self.assertEqual(None, retrieved_record) |
286 | |
287 | @@ -162,6 +193,7 @@ |
288 | record = Record({'record_number': 0}, record_type="http://example.com/") |
289 | self.assert_(not self.database.record_exists("ThisMustNotExist")) |
290 | record_id = self.database.put_record(record) |
291 | + self.maybe_die() # should be able to survive couchdb death |
292 | self.assert_(self.database.record_exists(record_id)) |
293 | self.database.delete_record(record_id) |
294 | self.assert_(not self.database.record_exists(record_id)) |
295 | @@ -171,12 +203,14 @@ |
296 | dictionary = {'record_number': 0, 'field1': 1, 'field2': 2} |
297 | record = Record(dictionary, record_type="http://example.com/") |
298 | record_id = self.database.put_record(record) |
299 | + self.maybe_die() # should be able to survive couchdb death |
300 | # manipulate the database 'out of view' |
301 | non_working_copy = self.database.get_record(record_id) |
302 | non_working_copy['field2'] = 22 |
303 | non_working_copy['field3'] = 3 |
304 | self.database.put_record(non_working_copy) |
305 | self.database.update_fields(record_id, {'field1': 11}) |
306 | + self.maybe_die() # should be able to survive couchdb death |
307 | working_copy = self.database.get_record(record_id) |
308 | self.assertEqual(0, working_copy['record_number']) |
309 | self.assertEqual(11, working_copy['field1']) |
310 | @@ -200,10 +234,12 @@ |
311 | self.assertRaises( |
312 | KeyError, self.database.delete_view, view2_name, design_doc) |
313 | self.database.add_view(view1_name, map_js, reduce_js, design_doc) |
314 | + self.maybe_die() # should be able to survive couchdb death |
315 | self.database.add_view(view2_name, map_js, reduce_js, design_doc) |
316 | self.database.delete_view(view1_name, design_doc) |
317 | self.assertRaises( |
318 | KeyError, self.database.delete_view, view1_name, design_doc) |
319 | + self.maybe_die() # should be able to survive couchdb death |
320 | self.database.delete_view(view2_name, design_doc) |
321 | self.assertRaises( |
322 | KeyError, self.database.delete_view, view2_name, design_doc) |
323 | @@ -237,6 +273,7 @@ |
324 | record_ids_we_care_about.remove(row.id) |
325 | self.assertFalse(row_is_deleted(row)) |
326 | |
327 | + self.maybe_die() # should be able to survive couchdb death |
328 | self.assertTrue(len(record_ids_we_care_about) == 0, "expected zero") |
329 | |
330 | self.assertRaises(KeyError, self.database.get_records, |
331 | @@ -250,6 +287,7 @@ |
332 | map_js = """function(doc) { emit(doc._id, null) }""" |
333 | self.database.add_view(view_name, map_js, None, design_doc) |
334 | |
335 | + self.maybe_die() # should be able to survive couchdb death |
336 | self.assertEqual(self.database.list_views(design_doc), [view_name]) |
337 | self.database.delete_view(view_name, design_doc) |
338 | |
339 | @@ -257,11 +295,13 @@ |
340 | |
341 | def test_get_view_by_type_new_but_already(self): |
342 | self.database.get_records(create_view=True) |
343 | + self.maybe_die() # should be able to survive couchdb death |
344 | self.database.get_records(create_view=True) |
345 | # No exceptions on second run? Yay. |
346 | |
347 | def test_get_view_by_type_createxcl_fail(self): |
348 | self.database.get_records(create_view=True) |
349 | + self.maybe_die() # should be able to survive couchdb death |
350 | self.assertRaises(KeyError, self.database.get_records, create_view=None) |
351 | |
352 | def test_get_changes(self): |
353 | @@ -321,6 +361,8 @@ |
354 | # Ensure time is same. |
355 | self.assertEqual(saved_time, self.database._changes_last_used) |
356 | |
357 | + ###self.maybe_die() # should be able to survive couchdb death |
358 | + |
359 | # Next time we run, we get the same event again. |
360 | # Consume queued changes. |
361 | count = self.database.report_changes(rep) |
362 | @@ -370,6 +412,7 @@ |
363 | constructed_record.attach(content, "nu/mbe/rs", "text/plain") |
364 | constructed_record.attach("string", "another document", "text/plain") |
365 | |
366 | + ###self.maybe_die() # should be able to survive couchdb death |
367 | constructed_record.attach("XXXXXXXXX", "never used", "text/plain") |
368 | constructed_record.detach("never used") # detach works before commit. |
369 | |
370 | @@ -382,6 +425,7 @@ |
371 | self.assertRaises(KeyError, constructed_record.attach, content, |
372 | "another document", "text/x-rst") |
373 | |
374 | + ###self.maybe_die() # should be able to survive couchdb death |
375 | record_id = self.database.put_record(constructed_record) |
376 | retrieved_record = self.database.get_record(record_id) |
377 | |
378 | @@ -402,6 +446,7 @@ |
379 | # get new |
380 | retrieved_record = self.database.get_record(record_id) |
381 | |
382 | + ###self.maybe_die() # should be able to survive couchdb death |
383 | # We can get a list of attachments. |
384 | self.assertEqual(set(retrieved_record.list_attachments()), |
385 | set(["nu/mbe/rs", "Document"])) |
386 | @@ -424,6 +469,7 @@ |
387 | self.assertEqual(out_data, content.getvalue()) |
388 | self.assertEqual(out_content_type, "text/plain") |
389 | |
390 | + ###self.maybe_die() # should be able to survive couchdb death |
391 | # Asking for a named document that does not exist causes KeyError. |
392 | self.assertRaises(KeyError, retrieved_record.attachment_data, |
393 | "NoExist") |
394 | @@ -454,6 +500,7 @@ |
395 | # ordinary requests are in key order |
396 | self.assertEqual(data, sorted(data)) |
397 | |
398 | + self.maybe_die() # should be able to survive couchdb death |
399 | # now request descending order and confirm that it *is* descending |
400 | descdata = [i.key for i in |
401 | list(self.database.execute_view(view1_name, design_doc, |
402 | @@ -513,3 +560,38 @@ |
403 | self.fail() |
404 | except FieldsConflict, e: |
405 | self.assertEqual({('field1',): (22, 11)}, e.conflicts) |
406 | + |
407 | + |
408 | +class TestServerDiesSegv(TestCouchDatabase): |
409 | + def get_test_context(self): |
410 | + try: |
411 | + return self.ctx |
412 | + except AttributeError: |
413 | + self.ctx = test_environment.create_new_test_environment() |
414 | + return self.ctx |
415 | + |
416 | + def maybe_die(self): |
417 | + self.database.ensure_full_commit() |
418 | + time.sleep(2) |
419 | + pid = find_pid(start_if_not_running=False, ctx=self.get_test_context()) |
420 | + if pid is None: |
421 | + print "couchdb has already quit! That's unexpected." |
422 | + return |
423 | + print "DIE, process", pid, "!" |
424 | + os.kill(pid, signal.SIGSEGV) |
425 | + self.wait_until_server_dead(pid=pid) |
426 | + |
427 | + |
428 | +class TestServerDiesNormal(TestCouchDatabase): |
429 | + def get_test_context(self): |
430 | + try: |
431 | + return self.ctx |
432 | + except AttributeError: |
433 | + self.ctx = test_environment.create_new_test_environment() |
434 | + return self.ctx |
435 | + |
436 | + def maybe_die(self): |
437 | + self.database.ensure_full_commit() |
438 | + time.sleep(2) |
439 | + stop_couchdb(ctx=self.get_test_context()) |
440 | + self.wait_until_server_dead() |
441 | |
442 | === modified file 'desktopcouch/start_local_couchdb.py' |
443 | --- desktopcouch/start_local_couchdb.py 2010-09-17 15:24:19 +0000 |
444 | +++ desktopcouch/start_local_couchdb.py 2010-09-22 14:14:44 +0000 |
445 | @@ -47,6 +47,8 @@ |
446 | import sys |
447 | import time |
448 | |
449 | +from gnomekeyring import NoKeyringDaemonError |
450 | + |
451 | import desktopcouch |
452 | from desktopcouch import local_files, read_pidfile, process_is_couchdb |
453 | from desktopcouch.records.server import CouchDatabase |
454 | @@ -216,7 +218,12 @@ |
455 | logging.info("Ubuntu SSO dbus service could not be used.") |
456 | |
457 | if sso: |
458 | - credentials = sso.find_credentials('Ubuntu One') |
459 | + try: |
460 | + credentials = sso.find_credentials('Ubuntu One') |
461 | + except NoKeyringDaemonError: |
462 | + logging.info( |
463 | + "NoKeyringDaemonError exception raised.") |
464 | + credentials = None |
465 | if credentials and len(credentials) > 0: |
466 | pair_with_ubuntuone() |
467 | |
468 | |
469 | === modified file 'desktopcouch/stop_local_couchdb.py' |
470 | --- desktopcouch/stop_local_couchdb.py 2010-02-04 22:31:41 +0000 |
471 | +++ desktopcouch/stop_local_couchdb.py 2010-09-22 14:14:44 +0000 |
472 | @@ -25,7 +25,7 @@ |
473 | from desktopcouch import local_files |
474 | |
475 | def stop_couchdb(ctx=local_files.DEFAULT_CONTEXT): |
476 | - local_exec = ctx.couch_exec_command + ["-k"] |
477 | + local_exec = ctx.couch_exec_command + ["-d"] |
478 | try: |
479 | retcode = subprocess.call(local_exec, shell=False) |
480 | if retcode < 0: |
Ooooh, live monkeys!
Code looks good, but the tests take ages now, and it looks like the server is killed and restarted way too often, though most of the tests seem to pass. Adding test output below. If this is all intentional, approved.
One test does fail, but I believe that's because I'm on lucid:
======= ======= ======= ======= ======= ======= ======= ======= ======= ======= ======= == pair.tests. test_ubuntuone_ pairing
[ERROR]: desktopcouch.
Traceback (most recent call last): python2. 6/dist- packages/ twisted/ trial/runner. py", line 563, in loadPackage python2. 6/dist- packages/ twisted/ python/ modules. py", line 381, in load pythonPath. moduleLoader( self.name) python2. 6/dist- packages/ twisted/ python/ reflect. py", line 464, in namedAny Stack(trialname ) eric/canonical/ desktopcouch/ r-vds/desktopco uch/pair/ tests/test_ ubuntuone_ pairing. py", line 19, in <module> ImportError: No module named mocker ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- --
File "/usr/lib/
module = modinfo.load()
File "/usr/lib/
return self.pathEntry.
File "/usr/lib/
topLevelPackage = _importAndCheck
File "/home/
from mocker import Mocker
exceptions.
-------
eric@eric- laptop: ~/canonical/ desktopcouch/ r-vds$ PYTHONPATH=. trial desktopcouch /tmp/tmpEaIpq4/ data/couchdb. html bookmarks. tests.test_ record ecord bookmark_ record ... [OK] feed_record ... [OK] folder_ record ... [OK] Record separator_ record ... [OK] contacts. tests.test_ contactspicker icker can_contruct_ contactspicker ... [OK] contacts. tests.test_ create create_ many_contacts ... [OK] head_or_ tails ... [OK] random_ bools ... [OK] contacts. tests.test_ record contact_ record ... [OK] contacts. tests.test_ view find_contact_ starting ... [OK] find_contacts_ exact ... [OK] notes.tests. test_record note_record ... [OK] pair.tests. test_couchdb_ io get_database_ names_replicata ble ... [OK] get_database_ names_replicata ble_bad_ server ... [SKIPPED] get_my_ host_unique_ id ... [OK] obsfuscation ... [OK]
Apache CouchDB has started, time to relax.
Browse your desktop CouchDB at file://
desktopcouch.
TestBookmarkR
test_
TestFeedRecord
test_
TestFolderRecord
test_
TestSeparator
test_
desktopcouch.
TestContactsP
test_
desktopcouch.
TestCreate
test_
test_
test_
desktopcouch.
TestContactRecord
test_
desktopcouch.
TestLocalFiles
test_
test_
desktopcouch.
TestNoteRecord
test_
desktopcouch.
TestCouchdbIo
test_
test_
test_
test_mkuri ... [OK]
test_
...