def test_sync_autoresolves(self): """ Test for sync autoresolve remote. This test was adapted because the remote database receives encrypted content and so it can't compare documents contents to autoresolve. """ # The remote database can't autoresolve conflicts based on magic # content convergence, so we modify this test to leave the possibility # of the remode document ending up in conflicted state. self.db1 = self.create_database('test1', 'source') self.db2 = self.create_database('test2', 'target') doc1 = self.db1.create_doc_from_json(tests.simple_doc, doc_id='doc') rev1 = doc1.rev doc2 = self.db2.create_doc_from_json(tests.simple_doc, doc_id='doc') rev2 = doc2.rev self.sync(self.db1, self.db2) doc = self.db1.get_doc('doc') self.assertFalse(doc.has_conflicts) # if remote content is in conflicted state, then document revisions # will be different. #self.assertEqual(doc.rev, self.db2.get_doc('doc').rev) v = vectorclock.VectorClockRev(doc.rev) self.assertTrue(v.is_newer(vectorclock.VectorClockRev(rev1))) self.assertTrue(v.is_newer(vectorclock.VectorClockRev(rev2)))
def test_sync_autoresolves_moar(self): # here we test that when a database that has a conflicted document is # the source of a sync, and the target database has a revision of the # conflicted document that is newer than the source database's, and # that target's database's document's content is the same as the # source's document's conflict's, the source's document's conflict gets # autoresolved, and the source's document's revision bumped. # # idea is as follows: # A B # a1 - # `-------> # a1 a1 # v v # a2 a1b1 # `-------> # a1b1+a2 a1b1 # v # a1b1+a2 a1b2 (a1b2 has same content as a2) # `-------> # a3b2 a1b2 (autoresolved) # `-------> # a3b2 a3b2 self.db1.create_doc(simple_doc, doc_id='doc') self.sync(self.db1, self.db2) for db, content in [(self.db1, '{}'), (self.db2, '{"hi": 42}')]: doc = db.get_doc('doc') doc.set_json(content) db.put_doc(doc) self.sync(self.db1, self.db2) # db1 and db2 now both have a doc of {hi:42}, but db1 has a conflict doc = self.db1.get_doc('doc') rev1 = doc.rev self.assertTrue(doc.has_conflicts) # set db2 to have a doc of {} (same as db1 before the conflict) doc = self.db2.get_doc('doc') doc.set_json('{}') self.db2.put_doc(doc) rev2 = doc.rev # sync it across self.sync(self.db1, self.db2) # tadaa! doc = self.db1.get_doc('doc') self.assertFalse(doc.has_conflicts) vec1 = vectorclock.VectorClockRev(rev1) vec2 = vectorclock.VectorClockRev(rev2) vec3 = vectorclock.VectorClockRev(doc.rev) self.assertTrue(vec3.is_newer(vec1)) self.assertTrue(vec3.is_newer(vec2)) # because the conflict is on the source, sync it another time self.sync(self.db1, self.db2) # make sure db2 now has the exact same thing self.assertEqual(self.db1.get_doc('doc'), self.db2.get_doc('doc'))
def test_sync_autoresolves(self): doc1 = self.db1.create_doc(simple_doc, doc_id='doc') rev1 = doc1.rev doc2 = self.db2.create_doc(simple_doc, doc_id='doc') rev2 = doc2.rev self.sync(self.db1, self.db2) doc = self.db1.get_doc('doc') self.assertFalse(doc.has_conflicts) self.assertEqual(doc.rev, self.db2.get_doc('doc').rev) v = vectorclock.VectorClockRev(doc.rev) self.assertTrue(v.is_newer(vectorclock.VectorClockRev(rev1))) self.assertTrue(v.is_newer(vectorclock.VectorClockRev(rev2)))
def test_resolve_simple(self): self.assertTrue(self.db.get_doc('my-doc').has_conflicts) cmd = self.make_command(client.CmdResolve) inf = cStringIO.StringIO(tests.nested_doc) cmd.run(self.db_path, 'my-doc', [self.doc1.rev, self.doc2.rev], inf) doc = self.db.get_doc('my-doc') vec = vectorclock.VectorClockRev(doc.rev) self.assertTrue(vec.is_newer(vectorclock.VectorClockRev( self.doc1.rev))) self.assertTrue(vec.is_newer(vectorclock.VectorClockRev( self.doc2.rev))) self.assertGetDoc(self.db, 'my-doc', doc.rev, tests.nested_doc, False) self.assertEqual('', cmd.stdout.getvalue()) self.assertEqual('rev: %s\n' % (doc.rev, ), cmd.stderr.getvalue())
def _force_doc_sync_conflict(self, doc): my_doc = self._get_doc(doc.doc_id) c = self._db_handle.cursor() self._prune_conflicts(doc, vectorclock.VectorClockRev(doc.rev)) self._add_conflict(c, doc.doc_id, my_doc.rev, my_doc.get_json()) doc.has_conflicts = True self._put_and_update_indexes(my_doc, doc)
def _force_doc_sync_conflict(self, doc): my_doc = self._get_doc(doc.doc_id) self._prune_conflicts(doc, vectorclock.VectorClockRev(doc.rev)) self._conflicts.setdefault(doc.doc_id, []).append( (my_doc.rev, my_doc.get_json())) doc.has_conflicts = True self._put_and_update_indexes(my_doc, doc)
def _prune_conflicts(self, doc, doc_vcr): """ Prune conflicts that are older then the current document's revision, or whose content match to the current document's content. Originally in u1db.CommonBackend :param doc: The document to have conflicts pruned. :type doc: ServerDocument :param doc_vcr: A vector clock representing the current document's revision. :type doc_vcr: u1db.vectorclock.VectorClock """ if doc.has_conflicts: autoresolved = False c_revs_to_prune = [] for c_doc in doc._conflicts: c_vcr = vectorclock.VectorClockRev(c_doc.rev) if doc_vcr.is_newer(c_vcr): c_revs_to_prune.append(c_doc.rev) elif doc.same_content_as(c_doc): c_revs_to_prune.append(c_doc.rev) doc_vcr.maximize(c_vcr) autoresolved = True if autoresolved: doc_vcr.increment(self._replica_uid) doc.rev = doc_vcr.as_str() doc.delete_conflicts(c_revs_to_prune)
def _force_doc_sync_conflict(self, doc): """ Add a conflict and force a document put. :param doc: The document to be put. :type doc: ServerDocument """ my_doc = self._get_doc(doc.doc_id) self._prune_conflicts(doc, vectorclock.VectorClockRev(doc.rev)) doc.add_conflict( self._factory(doc.doc_id, my_doc.rev, my_doc.get_json())) doc.has_conflicts = True self._put_doc(my_doc, doc)
def _prune_conflicts(self, doc, doc_vcr): if self._has_conflicts(doc.doc_id): autoresolved = False c_revs_to_prune = [] for c_doc in self._get_conflicts(doc.doc_id): c_vcr = vectorclock.VectorClockRev(c_doc.rev) if doc_vcr.is_newer(c_vcr): c_revs_to_prune.append(c_doc.rev) elif doc.same_content_as(c_doc): c_revs_to_prune.append(c_doc.rev) doc_vcr.maximize(c_vcr) autoresolved = True if autoresolved: doc_vcr.increment(self._replica_uid) doc.rev = doc_vcr.as_str() c = self._db_handle.cursor() self._delete_conflicts(c, doc, c_revs_to_prune)
def _prune_conflicts(self, doc, doc_vcr): if self._has_conflicts(doc.doc_id): autoresolved = False remaining_conflicts = [] cur_conflicts = self._conflicts[doc.doc_id] for c_rev, c_doc in cur_conflicts: c_vcr = vectorclock.VectorClockRev(c_rev) if doc_vcr.is_newer(c_vcr): continue if doc.same_content_as(Document(doc.doc_id, c_rev, c_doc)): doc_vcr.maximize(c_vcr) autoresolved = True continue remaining_conflicts.append((c_rev, c_doc)) if autoresolved: doc_vcr.increment(self._replica_uid) doc.rev = doc_vcr.as_str() self._replace_conflicts(doc, remaining_conflicts)