Merge lp:~pedronis/u1db/improve-reusability-of-sync-tests into lp:u1db
- improve-reusability-of-sync-tests
- Merge into trunk
Proposed by
Samuele Pedroni
Status: | Merged |
---|---|
Approved by: | Samuele Pedroni |
Approved revision: | 409 |
Merged at revision: | 407 |
Proposed branch: | lp:~pedronis/u1db/improve-reusability-of-sync-tests |
Merge into: | lp:u1db |
Diff against target: |
447 lines (+150/-19) 4 files modified
u1db/__init__.py (+10/-0) u1db/remote/http_target.py (+10/-0) u1db/tests/c_backend_wrapper.pyx (+2/-0) u1db/tests/test_sync.py (+128/-19) |
To merge this branch: | bzr merge lp:~pedronis/u1db/improve-reusability-of-sync-tests |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eric Casteleijn (community) | Approve | ||
Review via email: mp+125575@code.launchpad.net |
Commit message
improve reusuability of the sync tests
Description of the change
improve reusuability of the sync tests:
- distinguish tests that need shallow hooking into the target vs. tests that need to hook to follow the inner
working of sync_exchange
- in DatabaseSyncTests annotate database creations with their sync role (source, target, both)
To post a comment you must log in.
Revision history for this message
Eric Casteleijn (thisfred) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'u1db/__init__.py' |
2 | --- u1db/__init__.py 2012-09-17 16:35:19 +0000 |
3 | +++ u1db/__init__.py 2012-09-20 20:46:21 +0000 |
4 | @@ -671,3 +671,13 @@ |
5 | :param cb: A callable that takes cb(state) |
6 | """ |
7 | raise NotImplementedError(self._set_trace_hook) |
8 | + |
9 | + def _set_trace_hook_shallow(self, cb): |
10 | + """Set a callback that will be invoked to trace database actions. |
11 | + |
12 | + Similar to _set_trace_hook, for implementations that don't offer |
13 | + state changes from the inner working of sync_exchange(). |
14 | + |
15 | + :param cb: A callable that takes cb(state) |
16 | + """ |
17 | + self._set_trace_hook(cb) |
18 | |
19 | === modified file 'u1db/remote/http_target.py' |
20 | --- u1db/remote/http_target.py 2012-09-20 17:26:47 +0000 |
21 | +++ u1db/remote/http_target.py 2012-09-20 20:46:21 +0000 |
22 | @@ -51,6 +51,8 @@ |
23 | def record_sync_info(self, source_replica_uid, source_replica_generation, |
24 | source_transaction_id): |
25 | self._ensure_connection() |
26 | + if self._trace_hook: # for tests |
27 | + self._trace_hook('record_sync_info') |
28 | self._request_json('PUT', ['sync-from', source_replica_uid], {}, |
29 | {'generation': source_replica_generation, |
30 | 'transaction_id': source_transaction_id}) |
31 | @@ -88,6 +90,8 @@ |
32 | last_known_generation, last_known_trans_id, |
33 | return_doc_cb): |
34 | self._ensure_connection() |
35 | + if self._trace_hook: # for tests |
36 | + self._trace_hook('sync_exchange') |
37 | url = '%s/sync-from/%s' % (self._url.path, source_replica_uid) |
38 | self._conn.putrequest('POST', url) |
39 | self._conn.putheader('content-type', 'application/x-u1db-sync-stream') |
40 | @@ -120,3 +124,9 @@ |
41 | res = self._parse_sync_stream(data, return_doc_cb) |
42 | data = None |
43 | return res['new_generation'], res['new_transaction_id'] |
44 | + |
45 | + # for tests |
46 | + _trace_hook = None |
47 | + |
48 | + def _set_trace_hook_shallow(self, cb): |
49 | + self._trace_hook = cb |
50 | |
51 | === modified file 'u1db/tests/c_backend_wrapper.pyx' |
52 | --- u1db/tests/c_backend_wrapper.pyx 2012-08-15 15:36:30 +0000 |
53 | +++ u1db/tests/c_backend_wrapper.pyx 2012-09-20 20:46:21 +0000 |
54 | @@ -873,6 +873,8 @@ |
55 | handle_status("_set_trace_hook", |
56 | self._st._set_trace_hook(self._st, <void*>cb, _trace_hook)) |
57 | |
58 | + _set_trace_hook_shallow = _set_trace_hook |
59 | + |
60 | |
61 | cdef class CDatabase(object): |
62 | """A thin wrapper/shim to interact with the C implementation. |
63 | |
64 | === modified file 'u1db/tests/test_sync.py' |
65 | --- u1db/tests/test_sync.py 2012-08-15 14:51:29 +0000 |
66 | +++ u1db/tests/test_sync.py 2012-09-20 20:46:21 +0000 |
67 | @@ -24,6 +24,7 @@ |
68 | sync, |
69 | tests, |
70 | vectorclock, |
71 | + SyncTarget, |
72 | ) |
73 | from u1db.backends import ( |
74 | inmemory, |
75 | @@ -135,9 +136,11 @@ |
76 | self.other_changes.append( |
77 | (doc.doc_id, doc.rev, doc.get_json(), gen, trans_id)) |
78 | |
79 | - def set_trace_hook(self, callback): |
80 | + def set_trace_hook(self, callback, shallow=False): |
81 | + setter = (self.st._set_trace_hook if not shallow else |
82 | + self.st._set_trace_hook_shallow) |
83 | try: |
84 | - self.st._set_trace_hook(callback) |
85 | + setter(callback) |
86 | except NotImplementedError: |
87 | self.skipTest("%s does not implement _set_trace_hook" |
88 | % (self.st.__class__.__name__,)) |
89 | @@ -400,9 +403,34 @@ |
90 | ], |
91 | called) |
92 | |
93 | - |
94 | -def sync_via_synchronizer(test, db_source, db_target, trace_hook=None): |
95 | + def test__set_trace_hook_shallow(self): |
96 | + if (self.st._set_trace_hook_shallow == self.st._set_trace_hook |
97 | + or self.st._set_trace_hook_shallow.im_func == |
98 | + SyncTarget._set_trace_hook_shallow.im_func): |
99 | + # shallow same as full |
100 | + expected = ['before whats_changed', |
101 | + 'after whats_changed', |
102 | + 'before get_docs', |
103 | + 'record_sync_info', |
104 | + ] |
105 | + else: |
106 | + expected = ['sync_exchange', 'record_sync_info'] |
107 | + |
108 | + called = [] |
109 | + |
110 | + def cb(state): |
111 | + called.append(state) |
112 | + |
113 | + self.set_trace_hook(cb, shallow=True) |
114 | + self.st.sync_exchange([], 'replica', 0, None, self.receive_doc) |
115 | + self.st.record_sync_info('replica', 0, 'T-sid') |
116 | + self.assertEqual(expected, called) |
117 | + |
118 | + |
119 | +def sync_via_synchronizer(test, db_source, db_target, trace_hook=None, |
120 | + trace_hook_shallow=None): |
121 | target = db_target.get_sync_target() |
122 | + trace_hook = trace_hook or trace_hook_shallow |
123 | if trace_hook: |
124 | target._set_trace_hook(trace_hook) |
125 | return sync.Synchronizer(db_source, target).sync() |
126 | @@ -448,11 +476,13 @@ |
127 | |
128 | |
129 | def sync_via_synchronizer_and_http(test, db_source, db_target, |
130 | - trace_hook=None): |
131 | + trace_hook=None, trace_hook_shallow=None): |
132 | if trace_hook: |
133 | - test.skipTest("trace_hook unsupported over http") |
134 | + test.skipTest("full trace hook unsupported over http") |
135 | path = test._http_at[db_target] |
136 | target = http_target.HTTPSyncTarget.connect(test.getURL(path)) |
137 | + if trace_hook_shallow: |
138 | + target._set_trace_hook_shallow(trace_hook_shallow) |
139 | return sync.Synchronizer(db_source, target).sync() |
140 | |
141 | |
142 | @@ -467,8 +497,10 @@ |
143 | |
144 | if tests.c_backend_wrapper is not None: |
145 | # TODO: We should hook up sync tests with an HTTP target |
146 | - def sync_via_c_sync(test, db_source, db_target, trace_hook=None): |
147 | + def sync_via_c_sync(test, db_source, db_target, trace_hook=None, |
148 | + trace_hook_shallow=None): |
149 | target = db_target.get_sync_target() |
150 | + trace_hook = trace_hook or trace_hook_shallow |
151 | if trace_hook: |
152 | target._set_trace_hook(trace_hook) |
153 | return tests.c_backend_wrapper.sync_db_to_target(db_source, target) |
154 | @@ -488,13 +520,42 @@ |
155 | scenarios = sync_scenarios |
156 | do_sync = None # set by scenarios |
157 | |
158 | - def sync(self, db_source, db_target, trace_hook=None): |
159 | - return self.do_sync(self, db_source, db_target, trace_hook) |
160 | + def create_database(self, replica_uid, sync_role=None): |
161 | + if replica_uid != 'test': |
162 | + assert sync_role |
163 | + db = self.make_database_for_test(self, replica_uid) |
164 | + if sync_role: |
165 | + self._use_tracking[db] = (replica_uid, sync_role) |
166 | + return db |
167 | + |
168 | + def copy_database(self, db, sync_role=None): |
169 | + # DO NOT COPY OR REUSE THIS CODE OUTSIDE TESTS: COPYING U1DB DATABASES |
170 | + # IS THE WRONG THING TO DO, THE ONLY REASON WE DO SO HERE IS TO TEST |
171 | + # THAT WE CORRECTLY DETECT IT HAPPENING SO THAT WE CAN RAISE ERRORS |
172 | + # RATHER THAN CORRUPT USER DATA. USE SYNC INSTEAD, OR WE WILL SEND |
173 | + # NINJA TO YOUR HOUSE. |
174 | + db_copy = self.copy_database_for_test(self, db) |
175 | + name, orig_sync_role = self._use_tracking[db] |
176 | + self._use_tracking[db_copy] = (name + '(copy)', sync_role |
177 | + or orig_sync_role) |
178 | + return db_copy |
179 | + |
180 | + def sync(self, db_from, db_to, trace_hook=None, |
181 | + trace_hook_shallow=None): |
182 | + from_name, from_sync_role = self._use_tracking[db_from] |
183 | + to_name, to_sync_role = self._use_tracking[db_to] |
184 | + if from_sync_role not in ('source', 'both'): |
185 | + raise Exception("%s marked for %s use but used as source" % |
186 | + (from_name, from_sync_role)) |
187 | + if to_sync_role not in ('target', 'both'): |
188 | + raise Exception("%s marked for %s use but used as target" % |
189 | + (to_name, to_sync_role)) |
190 | + return self.do_sync(self, db_from, db_to, trace_hook, |
191 | + trace_hook_shallow) |
192 | |
193 | def setUp(self): |
194 | + self._use_tracking = {} |
195 | super(DatabaseSyncTests, self).setUp() |
196 | - self.db1 = self.create_database('test1') |
197 | - self.db2 = self.create_database('test2') |
198 | |
199 | def assertLastExchangeLog(self, db, expected): |
200 | log = getattr(db, '_last_exchange_log', None) |
201 | @@ -503,6 +564,8 @@ |
202 | self.assertEqual(expected, log) |
203 | |
204 | def test_sync_tracks_db_generation_of_other(self): |
205 | + self.db1 = self.create_database('test1', 'source') |
206 | + self.db2 = self.create_database('test2', 'target') |
207 | self.assertEqual(0, self.sync(self.db1, self.db2)) |
208 | self.assertEqual( |
209 | (0, ''), self.db1._get_replica_gen_and_trans_id('test2')) |
210 | @@ -513,6 +576,8 @@ |
211 | 'return': {'docs': [], 'last_gen': 0}}) |
212 | |
213 | def test_sync_autoresolves(self): |
214 | + self.db1 = self.create_database('test1', 'source') |
215 | + self.db2 = self.create_database('test2', 'target') |
216 | doc1 = self.db1.create_doc_from_json(simple_doc, doc_id='doc') |
217 | rev1 = doc1.rev |
218 | doc2 = self.db2.create_doc_from_json(simple_doc, doc_id='doc') |
219 | @@ -548,6 +613,8 @@ |
220 | # a3b2 a1b2 (autoresolved) |
221 | # `-------> |
222 | # a3b2 a3b2 |
223 | + self.db1 = self.create_database('test1', 'source') |
224 | + self.db2 = self.create_database('test2', 'target') |
225 | self.db1.create_doc_from_json(simple_doc, doc_id='doc') |
226 | self.sync(self.db1, self.db2) |
227 | for db, content in [(self.db1, '{}'), (self.db2, '{"hi": 42}')]: |
228 | @@ -600,6 +667,8 @@ |
229 | # a1b1+a2 a1b2 (a1b2 has same content as a2) |
230 | # <-------' |
231 | # a3b2 a3b2 (autoresolved and propagated) |
232 | + self.db1 = self.create_database('test1', 'both') |
233 | + self.db2 = self.create_database('test2', 'both') |
234 | self.db1.create_doc_from_json(simple_doc, doc_id='doc') |
235 | self.sync(self.db1, self.db2) |
236 | for db, content in [(self.db1, '{}'), (self.db2, '{"hi": 42}')]: |
237 | @@ -655,7 +724,9 @@ |
238 | # a2c1 a2+a1b1 a2c1 |
239 | # `-------> |
240 | # a2b2c1 a2b2c1 a2c1 |
241 | - self.db3 = self.create_database('test3') |
242 | + self.db1 = self.create_database('test1', 'source') |
243 | + self.db2 = self.create_database('test2', 'both') |
244 | + self.db3 = self.create_database('test3', 'target') |
245 | self.db1.create_doc_from_json(simple_doc, doc_id='doc') |
246 | self.sync(self.db1, self.db2) |
247 | self.sync(self.db2, self.db3) |
248 | @@ -703,6 +774,8 @@ |
249 | self.assertEqual(self.db1.get_doc('doc'), self.db2.get_doc('doc')) |
250 | |
251 | def test_sync_puts_changes(self): |
252 | + self.db1 = self.create_database('test1', 'source') |
253 | + self.db2 = self.create_database('test2', 'target') |
254 | doc = self.db1.create_doc_from_json(simple_doc) |
255 | self.assertEqual(1, self.sync(self.db1, self.db2)) |
256 | self.assertGetDoc(self.db2, doc.doc_id, doc.rev, simple_doc, False) |
257 | @@ -715,6 +788,8 @@ |
258 | 'return': {'docs': [], 'last_gen': 1}}) |
259 | |
260 | def test_sync_pulls_changes(self): |
261 | + self.db1 = self.create_database('test1', 'source') |
262 | + self.db2 = self.create_database('test2', 'target') |
263 | doc = self.db2.create_doc_from_json(simple_doc) |
264 | self.db1.create_index('test-idx', 'key') |
265 | self.assertEqual(0, self.sync(self.db1, self.db2)) |
266 | @@ -728,6 +803,8 @@ |
267 | self.assertEqual([doc], self.db1.get_from_index('test-idx', 'value')) |
268 | |
269 | def test_sync_pulling_doesnt_update_other_if_changed(self): |
270 | + self.db1 = self.create_database('test1', 'source') |
271 | + self.db2 = self.create_database('test2', 'target') |
272 | doc = self.db2.create_doc_from_json(simple_doc) |
273 | # After the local side has sent its list of docs, before we start |
274 | # receiving the "targets" response, we update the local database with a |
275 | @@ -754,6 +831,8 @@ |
276 | (0, ''), self.db2._get_replica_gen_and_trans_id('test1')) |
277 | |
278 | def test_sync_doesnt_update_other_if_nothing_pulled(self): |
279 | + self.db1 = self.create_database('test1', 'source') |
280 | + self.db2 = self.create_database('test2', 'target') |
281 | self.db1.create_doc_from_json(simple_doc) |
282 | |
283 | def no_record_sync_info(state): |
284 | @@ -761,14 +840,16 @@ |
285 | return |
286 | self.fail('SyncTarget.record_sync_info was called') |
287 | self.assertEqual(1, self.sync(self.db1, self.db2, |
288 | - trace_hook=no_record_sync_info)) |
289 | + trace_hook_shallow=no_record_sync_info)) |
290 | self.assertEqual( |
291 | 1, |
292 | self.db2._get_replica_gen_and_trans_id(self.db1._replica_uid)[0]) |
293 | |
294 | def test_sync_ignores_convergence(self): |
295 | + self.db1 = self.create_database('test1', 'source') |
296 | + self.db2 = self.create_database('test2', 'both') |
297 | doc = self.db1.create_doc_from_json(simple_doc) |
298 | - self.db3 = self.create_database('test3') |
299 | + self.db3 = self.create_database('test3', 'target') |
300 | self.assertEqual(1, self.sync(self.db1, self.db3)) |
301 | self.assertEqual(0, self.sync(self.db2, self.db3)) |
302 | self.assertEqual(1, self.sync(self.db1, self.db2)) |
303 | @@ -779,9 +860,11 @@ |
304 | 'return': {'docs': [], 'last_gen': 1}}) |
305 | |
306 | def test_sync_ignores_superseded(self): |
307 | + self.db1 = self.create_database('test1', 'both') |
308 | + self.db2 = self.create_database('test2', 'both') |
309 | doc = self.db1.create_doc_from_json(simple_doc) |
310 | doc_rev1 = doc.rev |
311 | - self.db3 = self.create_database('test3') |
312 | + self.db3 = self.create_database('test3', 'target') |
313 | self.sync(self.db1, self.db3) |
314 | self.sync(self.db2, self.db3) |
315 | new_content = '{"key": "altval"}' |
316 | @@ -798,6 +881,8 @@ |
317 | self.assertGetDoc(self.db1, doc.doc_id, doc_rev2, new_content, False) |
318 | |
319 | def test_sync_sees_remote_conflicted(self): |
320 | + self.db1 = self.create_database('test1', 'source') |
321 | + self.db2 = self.create_database('test2', 'target') |
322 | doc1 = self.db1.create_doc_from_json(simple_doc) |
323 | doc_id = doc1.doc_id |
324 | doc1_rev = doc1.rev |
325 | @@ -823,6 +908,8 @@ |
326 | self.assertEqual([], self.db1.get_from_index('test-idx', 'value')) |
327 | |
328 | def test_sync_sees_remote_delete_conflicted(self): |
329 | + self.db1 = self.create_database('test1', 'source') |
330 | + self.db2 = self.create_database('test2', 'target') |
331 | doc1 = self.db1.create_doc_from_json(simple_doc) |
332 | doc_id = doc1.doc_id |
333 | self.db1.create_index('test-idx', 'key') |
334 | @@ -847,6 +934,8 @@ |
335 | self.assertEqual([], self.db1.get_from_index('test-idx', 'value')) |
336 | |
337 | def test_sync_local_race_conflicted(self): |
338 | + self.db1 = self.create_database('test1', 'source') |
339 | + self.db2 = self.create_database('test2', 'target') |
340 | doc = self.db1.create_doc_from_json(simple_doc) |
341 | doc_id = doc.doc_id |
342 | doc1_rev = doc.rev |
343 | @@ -877,12 +966,14 @@ |
344 | self.assertEqual([], self.db1.get_from_index('test-idx', 'localval')) |
345 | |
346 | def test_sync_propagates_deletes(self): |
347 | + self.db1 = self.create_database('test1', 'source') |
348 | + self.db2 = self.create_database('test2', 'both') |
349 | doc1 = self.db1.create_doc_from_json(simple_doc) |
350 | doc_id = doc1.doc_id |
351 | self.db1.create_index('test-idx', 'key') |
352 | self.sync(self.db1, self.db2) |
353 | self.db2.create_index('test-idx', 'key') |
354 | - self.db3 = self.create_database('test3') |
355 | + self.db3 = self.create_database('test3', 'target') |
356 | self.sync(self.db1, self.db3) |
357 | self.db1.delete_doc(doc1) |
358 | deleted_rev = doc1.rev |
359 | @@ -908,8 +999,10 @@ |
360 | self.db3, doc_id, deleted_rev, None, False) |
361 | |
362 | def test_sync_propagates_resolution(self): |
363 | + self.db1 = self.create_database('test1', 'both') |
364 | + self.db2 = self.create_database('test2', 'both') |
365 | doc1 = self.db1.create_doc_from_json('{"a": 1}', doc_id='the-doc') |
366 | - db3 = self.create_database('test3') |
367 | + db3 = self.create_database('test3', 'both') |
368 | self.sync(self.db2, self.db1) |
369 | self.assertEqual( |
370 | self.db1._get_generation_info(), |
371 | @@ -945,7 +1038,9 @@ |
372 | self.assertFalse(doc3.has_conflicts) |
373 | |
374 | def test_sync_supersedes_conflicts(self): |
375 | - db3 = self.create_database('test3') |
376 | + self.db1 = self.create_database('test1', 'both') |
377 | + self.db2 = self.create_database('test2', 'target') |
378 | + db3 = self.create_database('test3', 'both') |
379 | doc1 = self.db1.create_doc_from_json('{"a": 1}', doc_id='the-doc') |
380 | self.db2.create_doc_from_json('{"b": 1}', doc_id='the-doc') |
381 | db3.create_doc_from_json('{"c": 1}', doc_id='the-doc') |
382 | @@ -971,15 +1066,19 @@ |
383 | self.assertEqual(3, len(db3.get_doc_conflicts('the-doc'))) |
384 | |
385 | def test_sync_stops_after_get_sync_info(self): |
386 | + self.db1 = self.create_database('test1', 'source') |
387 | + self.db2 = self.create_database('test2', 'target') |
388 | self.db1.create_doc_from_json(tests.simple_doc) |
389 | self.sync(self.db1, self.db2) |
390 | |
391 | def put_hook(state): |
392 | self.fail("Tracehook triggered for %s" % (state,)) |
393 | |
394 | - self.sync(self.db1, self.db2, trace_hook=put_hook) |
395 | + self.sync(self.db1, self.db2, trace_hook_shallow=put_hook) |
396 | |
397 | def test_sync_detects_rollback_in_source(self): |
398 | + self.db1 = self.create_database('test1', 'source') |
399 | + self.db2 = self.create_database('test2', 'target') |
400 | self.db1.create_doc_from_json(tests.simple_doc, doc_id='doc1') |
401 | self.sync(self.db1, self.db2) |
402 | db1_copy = self.copy_database(self.db1) |
403 | @@ -989,6 +1088,8 @@ |
404 | errors.InvalidGeneration, self.sync, db1_copy, self.db2) |
405 | |
406 | def test_sync_detects_rollback_in_target(self): |
407 | + self.db1 = self.create_database('test1', 'source') |
408 | + self.db2 = self.create_database('test2', 'target') |
409 | self.db1.create_doc_from_json(tests.simple_doc, doc_id="divergent") |
410 | self.sync(self.db1, self.db2) |
411 | db2_copy = self.copy_database(self.db2) |
412 | @@ -998,6 +1099,8 @@ |
413 | errors.InvalidGeneration, self.sync, self.db1, db2_copy) |
414 | |
415 | def test_sync_detects_diverged_source(self): |
416 | + self.db1 = self.create_database('test1', 'source') |
417 | + self.db2 = self.create_database('test2', 'target') |
418 | db3 = self.copy_database(self.db1) |
419 | self.db1.create_doc_from_json(tests.simple_doc, doc_id="divergent") |
420 | db3.create_doc_from_json(tests.simple_doc, doc_id="divergent") |
421 | @@ -1006,6 +1109,8 @@ |
422 | errors.InvalidTransactionId, self.sync, db3, self.db2) |
423 | |
424 | def test_sync_detects_diverged_target(self): |
425 | + self.db1 = self.create_database('test1', 'source') |
426 | + self.db2 = self.create_database('test2', 'target') |
427 | db3 = self.copy_database(self.db2) |
428 | db3.create_doc_from_json(tests.nested_doc, doc_id="divergent") |
429 | self.db1.create_doc_from_json(tests.simple_doc, doc_id="divergent") |
430 | @@ -1014,6 +1119,8 @@ |
431 | errors.InvalidTransactionId, self.sync, self.db1, db3) |
432 | |
433 | def test_sync_detects_rollback_and_divergence_in_source(self): |
434 | + self.db1 = self.create_database('test1', 'source') |
435 | + self.db2 = self.create_database('test2', 'target') |
436 | self.db1.create_doc_from_json(tests.simple_doc, doc_id='doc1') |
437 | self.sync(self.db1, self.db2) |
438 | db1_copy = self.copy_database(self.db1) |
439 | @@ -1026,6 +1133,8 @@ |
440 | errors.InvalidTransactionId, self.sync, db1_copy, self.db2) |
441 | |
442 | def test_sync_detects_rollback_and_divergence_in_target(self): |
443 | + self.db1 = self.create_database('test1', 'source') |
444 | + self.db2 = self.create_database('test2', 'target') |
445 | self.db1.create_doc_from_json(tests.simple_doc, doc_id="divergent") |
446 | self.sync(self.db1, self.db2) |
447 | db2_copy = self.copy_database(self.db2) |