summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordrebs <drebs@leap.se>2013-01-09 11:46:58 -0200
committerdrebs <drebs@leap.se>2013-01-09 11:46:58 -0200
commit8e32fdb0be5d34c6554a8c0f75bdf8bf0debcd4a (patch)
treed3b26ee128674b1484db12f7f421facb8724f676
parent11d4d9ece76260639d2c2815b694d6cd68109965 (diff)
CouchDatabase passes u1db LocalDatabaseTests.
-rw-r--r--backends/couch.py8
-rw-r--r--backends/objectstore.py69
-rw-r--r--tests/test_couch.py83
-rw-r--r--tests/test_logs.py28
-rw-r--r--util.py17
5 files changed, 172 insertions, 33 deletions
diff --git a/backends/couch.py b/backends/couch.py
index 8603a36b..14021737 100644
--- a/backends/couch.py
+++ b/backends/couch.py
@@ -58,6 +58,8 @@ class CouchDatabase(ObjectStore):
generation = self._get_generation()
results = []
for doc_id in self._database:
+ if doc_id == self.U1DB_DATA_DOC_ID:
+ continue
doc = self._get_doc(doc_id)
if doc.content is None and not include_deleted:
continue
@@ -106,13 +108,15 @@ class CouchDatabase(ObjectStore):
content = json.loads(cdoc['u1db_json'])
self._sync_log.log = content['sync_log']
self._transaction_log.log = content['transaction_log']
+ self._conflict_log.log = content['conflict_log']
self._replica_uid = content['replica_uid']
self._couch_rev = cdoc['_rev']
def _set_u1db_data(self):
doc = self._factory(doc_id=self.U1DB_DATA_DOC_ID)
- doc.content = { 'transaction_log' : self._transaction_log.log,
- 'sync_log' : self._sync_log.log,
+ doc.content = { 'sync_log' : self._sync_log.log,
+ 'transaction_log' : self._transaction_log.log,
+ 'conflict_log' : self._conflict_log.log,
'replica_uid' : self._replica_uid,
'_rev' : self._couch_rev}
self._put_doc(doc)
diff --git a/backends/objectstore.py b/backends/objectstore.py
index 54ffa9dd..cd051588 100644
--- a/backends/objectstore.py
+++ b/backends/objectstore.py
@@ -16,6 +16,7 @@ class ObjectStore(CommonBackend):
self.set_document_factory(Document)
self._sync_log = soledadutil.SyncLog()
self._transaction_log = soledadutil.TransactionLog()
+ self._conflict_log = soledadutil.ConflictLog()
self._replica_uid = replica_uid
self._ensure_u1db_data()
@@ -44,6 +45,12 @@ class ObjectStore(CommonBackend):
def _put_doc(self, doc):
raise NotImplementedError(self._put_doc)
+ def _update_gen_and_transaction_log(self, doc_id):
+ new_gen = self._get_generation() + 1
+ trans_id = self._allocate_transaction_id()
+ self._transaction_log.append((new_gen, doc_id, trans_id))
+ self._set_u1db_data()
+
def put_doc(self, doc):
# consistency check
if doc.doc_id is None:
@@ -66,11 +73,7 @@ class ObjectStore(CommonBackend):
new_rev = self._allocate_doc_rev(doc.rev)
doc.rev = new_rev
self._put_doc(doc)
- # update u1db generation and logs
- new_gen = self._get_generation() + 1
- trans_id = self._allocate_transaction_id()
- self._transaction_log.append((new_gen, doc.doc_id, trans_id))
- self._set_u1db_data()
+ self._update_gen_and_transaction_log(doc.doc_id)
return doc.rev
def delete_doc(self, doc):
@@ -87,6 +90,7 @@ class ObjectStore(CommonBackend):
doc.rev = new_rev
doc.make_tombstone()
self._put_doc(doc)
+ self._update_gen_and_transaction_log(doc.doc_id)
return new_rev
# start of index-related methods: these are not supported by this backend.
@@ -130,6 +134,16 @@ class ObjectStore(CommonBackend):
other_transaction_id)
self._set_u1db_data()
+ def _do_set_replica_gen_and_trans_id(self, other_replica_uid,
+ other_generation, other_transaction_id):
+ return self._set_replica_gen_and_trans_id(
+ other_replica_uid,
+ other_generation,
+ other_transaction_id)
+
+ def _get_transaction_log(self):
+ return self._transaction_log.get_transaction_log()
+
#-------------------------------------------------------------------------
# implemented methods from CommonBackend
#-------------------------------------------------------------------------
@@ -146,9 +160,10 @@ class ObjectStore(CommonBackend):
# Documents never have conflicts on server.
return False
- def _put_and_update_indexes(self, doc_id, old_doc, new_rev, content):
- raise NotImplementedError(self._put_and_update_indexes)
-
+ def _put_and_update_indexes(self, old_doc, doc):
+ # TODO: implement index update
+ self._put_doc(doc)
+ self._update_gen_and_transaction_log(doc.doc_id)
def _get_trans_id_for_gen(self, generation):
self._get_u1db_data()
@@ -187,8 +202,9 @@ class ObjectStore(CommonBackend):
if self._replica_uid is None:
self._replica_uid = uuid.uuid4().hex
doc = self._factory(doc_id=self.U1DB_DATA_DOC_ID)
- doc.content = { 'transaction_log' : [],
- 'sync_log' : [],
+ doc.content = { 'sync_log' : [],
+ 'transaction_log' : [],
+ 'conflict_log' : [],
'replica_uid' : self._replica_uid }
self._put_doc(doc)
@@ -213,3 +229,36 @@ class ObjectStore(CommonBackend):
replica_uid = property(
_get_replica_uid, _set_replica_uid, doc="Replica UID of the database")
+
+
+ #-------------------------------------------------------------------------
+ # The methods below were cloned from u1db sqlite backend. They should at
+ # least exist and raise a NotImplementedError exception in CommonBackend
+ # (should we maybe fill a bug in u1db bts?).
+ #-------------------------------------------------------------------------
+
+ def _add_conflict(self, doc_id, my_doc_rev, my_content):
+ self._conflict_log.append((doc_id, my_doc_rev, my_content))
+
+ def _delete_conflicts(self, doc, conflict_revs):
+ deleting = [(doc.doc_id, c_rev) for c_rev in conflict_revs]
+ self._conflict_log.delete_conflicts(deleting)
+ doc.has_conflicts = self._has_conflicts(doc.doc_id)
+
+ 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)
diff --git a/tests/test_couch.py b/tests/test_couch.py
index 6a2c7dab..3f6c45f6 100644
--- a/tests/test_couch.py
+++ b/tests/test_couch.py
@@ -1,4 +1,8 @@
-"""Test ObjectStore backend bits."""
+"""Test ObjectStore backend bits.
+
+For these tests to run, a couch server has to be running on (default) port
+5984.
+"""
import sys
import copy
@@ -6,8 +10,20 @@ import testtools
import testscenarios
from leap.soledad.backends import couch
from leap.soledad.tests import u1db_tests as tests
-from leap.soledad.tests.u1db_tests.test_backends import AllDatabaseTests
+from leap.soledad.tests.u1db_tests.test_backends import (
+ TestAlternativeDocument,
+ AllDatabaseTests,
+ LocalDatabaseTests,
+ LocalDatabaseValidateGenNTransIdTests,
+ LocalDatabaseValidateSourceGenTests,
+ LocalDatabaseWithConflictsTests,
+ DatabaseIndexTests,
+)
+
+#-----------------------------------------------------------------------------
+# The following tests come from `u1db.tests.test_common_backends`.
+#-----------------------------------------------------------------------------
class TestCouchBackendImpl(tests.TestCase):
@@ -19,6 +35,11 @@ class TestCouchBackendImpl(tests.TestCase):
int(doc_id1[len('D-'):], 16)
self.assertNotEqual(doc_id1, db._allocate_doc_id())
+
+#-----------------------------------------------------------------------------
+# The following tests come from `u1db.tests.test_backends`.
+#-----------------------------------------------------------------------------
+
def make_couch_database_for_test(test, replica_uid, path='test'):
return couch.CouchDatabase('http://localhost:5984', 'u1db_tests',
replica_uid=replica_uid)
@@ -34,28 +55,66 @@ def copy_couch_database_for_test(test, db):
new_db._ensure_u1db_data()
return new_db
-def make_couch_app(test):
- pass
-
-class CouchTests(AllDatabaseTests):
-
- scenarios = [
+COUCH_SCENARIOS = [
('couch', {'make_database_for_test': make_couch_database_for_test,
'copy_database_for_test': copy_couch_database_for_test,
'make_document_for_test': tests.make_document_for_test,}),
]
+
+class CouchTests(AllDatabaseTests):
+
+ scenarios = COUCH_SCENARIOS
+
def tearDown(self):
self.db.delete_database()
super(CouchTests, self).tearDown()
- #make_database_for_test = make_couch_database_for_test
- #copy_database_for_test = copy_couch_database_for_test
+class CouchDatabaseTests(LocalDatabaseTests):
+
+ scenarios = COUCH_SCENARIOS
+
+ def tearDown(self):
+ self.db.delete_database()
+ super(CouchDatabaseTests, self).tearDown()
+
-# def runTest(self):
-# pass
+#class CouchValidateGenNTransIdTests(LocalDatabaseValidateGenNTransIdTests):
+#
+# scenarios = COUCH_SCENARIOS
+#
+# def tearDown(self):
+# self.db.delete_database()
+# super(CouchTests, self).tearDown()
+#
+#
+#class CouchValidateSourceGenTests(LocalDatabaseValidateSourceGenTests):
+#
+# scenarios = COUCH_SCENARIOS
+#
+# def tearDown(self):
+# self.db.delete_database()
+# super(CouchTests, self).tearDown()
+#
+#
+#class CouchWithConflictsTests(LocalDatabaseWithConflictsTests):
+#
+# scenarios = COUCH_SCENARIOS
+#
+# def tearDown(self):
+# self.db.delete_database()
+# super(CouchTests, self).tearDown()
+#
+#
+#class CouchIndexTests(DatabaseIndexTests):
+#
+# scenarios = COUCH_SCENARIOS
+#
+# def tearDown(self):
+# self.db.delete_database()
+# super(CouchTests, self).tearDown()
#
load_tests = tests.load_with_scenarios
diff --git a/tests/test_logs.py b/tests/test_logs.py
index 072ac1a5..7fbb1cb7 100644
--- a/tests/test_logs.py
+++ b/tests/test_logs.py
@@ -1,5 +1,5 @@
import unittest2 as unittest
-from leap.soledad.util import TransactionLog, SyncLog
+from leap.soledad.util import TransactionLog, SyncLog, ConflictLog
class LogTestCase(unittest.TestCase):
@@ -49,25 +49,37 @@ class LogTestCase(unittest.TestCase):
def test_whats_changed(self):
data = [
- (2, "doc_3", "tran_3"),
- (3, "doc_2", "tran_2"),
- (1, "doc_1", "tran_1")
- ]
+ (1, "doc_1", "tran_1"),
+ (2, "doc_2", "tran_2"),
+ (3, "doc_3", "tran_3")
+ ]
log = TransactionLog()
log.log = data
self.assertEqual(
log.whats_changed(3),
- (3, "tran_2", []),
+ (3, "tran_3", []),
'error getting whats changed.')
self.assertEqual(
log.whats_changed(2),
- (3, "tran_2", [("doc_2",3,"tran_2")]),
+ (3, "tran_3", [("doc_3",3,"tran_3")]),
'error getting whats changed.')
self.assertEqual(
log.whats_changed(1),
- (3, "tran_2", [("doc_3",2,"tran_3"),("doc_2",3,"tran_2")]),
+ (3, "tran_3", [("doc_2",2,"tran_2"),("doc_3",3,"tran_3")]),
'error getting whats changed.')
+ def test_conflict_log(self):
+ data = [('1', 'my:1', 'irrelevant'),
+ ('2', 'my:1', 'irrelevant'),
+ ('3', 'my:1', 'irrelevant')]
+ log = ConflictLog()
+ log.log = data
+ log.delete_conflicts([('1','my:1'),('2','my:1')])
+ self.assertEqual(
+ log.log,
+ [('3', 'my:1', 'irrelevant')],
+ 'error deleting conflicts.')
+
if __name__ == '__main__':
unittest.main()
diff --git a/util.py b/util.py
index af38cd76..8683fbb9 100644
--- a/util.py
+++ b/util.py
@@ -149,7 +149,13 @@ class TransactionLog(SimpleLog):
cur_gen, _, newest_trans_id = results[0]
return cur_gen, newest_trans_id, changes
-
+
+
+ def get_transaction_log(self):
+ """
+ Return only a list of (doc_id, transaction_id)
+ """
+ return map(lambda x: (x[1], x[2]), sorted(self._log))
class SyncLog(SimpleLog):
@@ -182,3 +188,12 @@ class SyncLog(SimpleLog):
self.append((other_replica_uid, other_generation,
other_transaction_id))
+class ConflictLog(SimpleLog):
+ """
+ A list of (doc_id, my_doc_rev, my_content) tuples.
+ """
+
+ def delete_conflicts(self, conflicts):
+ for conflict in conflicts:
+ self.log = self.filter(lambda x:
+ x[0] != conflict[0] or x[1] != conflict[1])