| #!/usr/bin/python2.4 |
| # Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Tests exercising chromiumsync and SyncDataModel.""" |
| |
| import unittest |
| |
| import autofill_specifics_pb2 |
| import chromiumsync |
| import sync_pb2 |
| import theme_specifics_pb2 |
| |
| class SyncDataModelTest(unittest.TestCase): |
| def setUp(self): |
| self.model = chromiumsync.SyncDataModel() |
| |
| def AddToModel(self, proto): |
| self.model._entries[proto.id_string] = proto |
| |
| def GetChangesFromTimestamp(self, requested_types, timestamp): |
| message = sync_pb2.GetUpdatesMessage() |
| message.from_timestamp = timestamp |
| for data_type in requested_types: |
| message.requested_types.Extensions[ |
| chromiumsync.SYNC_TYPE_TO_EXTENSION[data_type]].SetInParent() |
| return self.model.GetChanges(chromiumsync.UpdateSieve(message)) |
| |
| def testPermanentItemSpecs(self): |
| specs = chromiumsync.SyncDataModel._PERMANENT_ITEM_SPECS |
| |
| declared_specs = set(['0']) |
| for spec in specs: |
| self.assertTrue(spec.parent_tag in declared_specs, 'parent tags must ' |
| 'be declared before use') |
| declared_specs.add(spec.tag) |
| |
| unique_datatypes = set([x.sync_type for x in specs]) |
| self.assertEqual(unique_datatypes, set(chromiumsync.ALL_TYPES), |
| 'Every sync datatype should have a permanent folder ' |
| 'associated with it') |
| |
| def testSaveEntry(self): |
| proto = sync_pb2.SyncEntity() |
| proto.id_string = 'abcd' |
| proto.version = 0 |
| self.assertFalse(self.model._ItemExists(proto.id_string)) |
| self.model._SaveEntry(proto) |
| self.assertEqual(1, proto.version) |
| self.assertTrue(self.model._ItemExists(proto.id_string)) |
| self.model._SaveEntry(proto) |
| self.assertEqual(2, proto.version) |
| proto.version = 0 |
| self.assertTrue(self.model._ItemExists(proto.id_string)) |
| self.assertEqual(2, self.model._entries[proto.id_string].version) |
| |
| def testWritePosition(self): |
| def MakeProto(id_string, parent, position): |
| proto = sync_pb2.SyncEntity() |
| proto.id_string = id_string |
| proto.position_in_parent = position |
| proto.parent_id_string = parent |
| self.AddToModel(proto) |
| |
| MakeProto('a', 'X', 1000) |
| MakeProto('b', 'X', 1800) |
| MakeProto('c', 'X', 2600) |
| MakeProto('a1', 'Z', 1007) |
| MakeProto('a2', 'Z', 1807) |
| MakeProto('a3', 'Z', 2607) |
| MakeProto('s', 'Y', 10000) |
| |
| def AssertPositionResult(my_id, parent_id, prev_id, expected_position): |
| entry = sync_pb2.SyncEntity() |
| entry.id_string = my_id |
| self.model._WritePosition(entry, parent_id, prev_id) |
| self.assertEqual(expected_position, entry.position_in_parent) |
| self.assertEqual(parent_id, entry.parent_id_string) |
| self.assertFalse(entry.HasField('insert_after_item_id')) |
| |
| AssertPositionResult('new', 'new_parent', '', 0) |
| AssertPositionResult('new', 'Y', '', 10000 - (2 ** 20)) |
| AssertPositionResult('new', 'Y', 's', 10000 + (2 ** 20)) |
| AssertPositionResult('s', 'Y', '', 10000) |
| AssertPositionResult('s', 'Y', 's', 10000) |
| AssertPositionResult('a1', 'Z', '', 1007) |
| |
| AssertPositionResult('new', 'X', '', 1000 - (2 ** 20)) |
| AssertPositionResult('new', 'X', 'a', 1100) |
| AssertPositionResult('new', 'X', 'b', 1900) |
| AssertPositionResult('new', 'X', 'c', 2600 + (2 ** 20)) |
| |
| AssertPositionResult('a1', 'X', '', 1000 - (2 ** 20)) |
| AssertPositionResult('a1', 'X', 'a', 1100) |
| AssertPositionResult('a1', 'X', 'b', 1900) |
| AssertPositionResult('a1', 'X', 'c', 2600 + (2 ** 20)) |
| |
| AssertPositionResult('a', 'X', '', 1000) |
| AssertPositionResult('a', 'X', 'b', 1900) |
| AssertPositionResult('a', 'X', 'c', 2600 + (2 ** 20)) |
| |
| AssertPositionResult('b', 'X', '', 1000 - (2 ** 20)) |
| AssertPositionResult('b', 'X', 'a', 1800) |
| AssertPositionResult('b', 'X', 'c', 2600 + (2 ** 20)) |
| |
| AssertPositionResult('c', 'X', '', 1000 - (2 ** 20)) |
| AssertPositionResult('c', 'X', 'a', 1100) |
| AssertPositionResult('c', 'X', 'b', 2600) |
| |
| def testCreatePermanentItems(self): |
| self.model._CreatePermanentItems(chromiumsync.ALL_TYPES) |
| self.assertEqual(len(chromiumsync.ALL_TYPES) + 2, |
| len(self.model._entries)) |
| |
| def ExpectedPermanentItemCount(self, sync_type): |
| if sync_type == chromiumsync.BOOKMARK: |
| return 4 |
| elif sync_type == chromiumsync.TOP_LEVEL: |
| return 1 |
| else: |
| return 2 |
| |
| def testGetChangesFromTimestampZeroForEachType(self): |
| all_types = chromiumsync.ALL_TYPES[1:] |
| for sync_type in all_types: |
| self.model = chromiumsync.SyncDataModel() |
| request_types = [sync_type] |
| |
| version, changes, remaining = ( |
| self.GetChangesFromTimestamp(request_types, 0)) |
| |
| expected_count = self.ExpectedPermanentItemCount(sync_type) |
| self.assertEqual(expected_count, version) |
| self.assertEqual(expected_count, len(changes)) |
| self.assertEqual('google_chrome', changes[0].server_defined_unique_tag) |
| for change in changes: |
| self.assertTrue(change.HasField('server_defined_unique_tag')) |
| self.assertEqual(change.version, change.sync_timestamp) |
| self.assertTrue(change.version <= version) |
| |
| # Test idempotence: another GetUpdates from ts=0 shouldn't recreate. |
| version, changes, remaining = ( |
| self.GetChangesFromTimestamp(request_types, 0)) |
| self.assertEqual(expected_count, version) |
| self.assertEqual(expected_count, len(changes)) |
| self.assertEqual(0, remaining) |
| |
| # Doing a wider GetUpdates from timestamp zero shouldn't recreate either. |
| new_version, changes, remaining = ( |
| self.GetChangesFromTimestamp(all_types, 0)) |
| self.assertEqual(len(chromiumsync.SyncDataModel._PERMANENT_ITEM_SPECS), |
| new_version) |
| self.assertEqual(new_version, len(changes)) |
| self.assertEqual(0, remaining) |
| version, changes, remaining = ( |
| self.GetChangesFromTimestamp(request_types, 0)) |
| self.assertEqual(new_version, version) |
| self.assertEqual(expected_count, len(changes)) |
| self.assertEqual(0, remaining) |
| |
| def testBatchSize(self): |
| for sync_type in chromiumsync.ALL_TYPES[1:]: |
| specifics = chromiumsync.GetDefaultEntitySpecifics(sync_type) |
| self.model = chromiumsync.SyncDataModel() |
| request_types = [sync_type] |
| |
| for i in range(self.model._BATCH_SIZE*3): |
| entry = sync_pb2.SyncEntity() |
| entry.id_string = 'batch test %d' % i |
| entry.specifics.CopyFrom(specifics) |
| self.model._SaveEntry(entry) |
| last_bit = self.ExpectedPermanentItemCount(sync_type) |
| version, changes, changes_remaining = ( |
| self.GetChangesFromTimestamp(request_types, 0)) |
| self.assertEqual(self.model._BATCH_SIZE, version) |
| self.assertEqual(self.model._BATCH_SIZE*2 + last_bit, changes_remaining) |
| version, changes, changes_remaining = ( |
| self.GetChangesFromTimestamp(request_types, version)) |
| self.assertEqual(self.model._BATCH_SIZE*2, version) |
| self.assertEqual(self.model._BATCH_SIZE + last_bit, changes_remaining) |
| version, changes, changes_remaining = ( |
| self.GetChangesFromTimestamp(request_types, version)) |
| self.assertEqual(self.model._BATCH_SIZE*3, version) |
| self.assertEqual(last_bit, changes_remaining) |
| version, changes, changes_remaining = ( |
| self.GetChangesFromTimestamp(request_types, version)) |
| self.assertEqual(self.model._BATCH_SIZE*3 + last_bit, version) |
| self.assertEqual(0, changes_remaining) |
| |
| # Now delete a third of the items. |
| for i in xrange(self.model._BATCH_SIZE*3 - 1, 0, -3): |
| entry = sync_pb2.SyncEntity() |
| entry.id_string = 'batch test %d' % i |
| entry.deleted = True |
| self.model._SaveEntry(entry) |
| |
| # The batch counts shouldn't change. |
| version, changes, changes_remaining = ( |
| self.GetChangesFromTimestamp(request_types, 0)) |
| self.assertEqual(self.model._BATCH_SIZE, len(changes)) |
| self.assertEqual(self.model._BATCH_SIZE*2 + last_bit, changes_remaining) |
| version, changes, changes_remaining = ( |
| self.GetChangesFromTimestamp(request_types, version)) |
| self.assertEqual(self.model._BATCH_SIZE, len(changes)) |
| self.assertEqual(self.model._BATCH_SIZE + last_bit, changes_remaining) |
| version, changes, changes_remaining = ( |
| self.GetChangesFromTimestamp(request_types, version)) |
| self.assertEqual(self.model._BATCH_SIZE, len(changes)) |
| self.assertEqual(last_bit, changes_remaining) |
| version, changes, changes_remaining = ( |
| self.GetChangesFromTimestamp(request_types, version)) |
| self.assertEqual(last_bit, len(changes)) |
| self.assertEqual(self.model._BATCH_SIZE*4 + last_bit, version) |
| self.assertEqual(0, changes_remaining) |
| |
| def testCommitEachDataType(self): |
| for sync_type in chromiumsync.ALL_TYPES[1:]: |
| specifics = chromiumsync.GetDefaultEntitySpecifics(sync_type) |
| self.model = chromiumsync.SyncDataModel() |
| my_cache_guid = '112358132134' |
| parent = 'foobar' |
| commit_session = {} |
| |
| # Start with a GetUpdates from timestamp 0, to populate permanent items. |
| original_version, original_changes, changes_remaining = ( |
| self.GetChangesFromTimestamp([sync_type], 0)) |
| |
| def DoCommit(original=None, id_string='', name=None, parent=None, |
| prev=None): |
| proto = sync_pb2.SyncEntity() |
| if original is not None: |
| proto.version = original.version |
| proto.id_string = original.id_string |
| proto.parent_id_string = original.parent_id_string |
| proto.name = original.name |
| else: |
| proto.id_string = id_string |
| proto.version = 0 |
| proto.specifics.CopyFrom(specifics) |
| if name is not None: |
| proto.name = name |
| if parent: |
| proto.parent_id_string = parent.id_string |
| if prev: |
| proto.insert_after_item_id = prev.id_string |
| else: |
| proto.insert_after_item_id = '' |
| proto.folder = True |
| proto.deleted = False |
| result = self.model.CommitEntry(proto, my_cache_guid, commit_session) |
| self.assertTrue(result) |
| return (proto, result) |
| |
| # Commit a new item. |
| proto1, result1 = DoCommit(name='namae', id_string='Foo', |
| parent=original_changes[-1]) |
| # Commit an item whose parent is another item (referenced via the |
| # pre-commit ID). |
| proto2, result2 = DoCommit(name='Secondo', id_string='Bar', |
| parent=proto1) |
| # Commit a sibling of the second item. |
| proto3, result3 = DoCommit(name='Third!', id_string='Baz', |
| parent=proto1, prev=proto2) |
| |
| self.assertEqual(3, len(commit_session)) |
| for p, r in [(proto1, result1), (proto2, result2), (proto3, result3)]: |
| self.assertNotEqual(r.id_string, p.id_string) |
| self.assertEqual(r.originator_client_item_id, p.id_string) |
| self.assertEqual(r.originator_cache_guid, my_cache_guid) |
| self.assertTrue(r is not self.model._entries[r.id_string], |
| "Commit result didn't make a defensive copy.") |
| self.assertTrue(p is not self.model._entries[r.id_string], |
| "Commit result didn't make a defensive copy.") |
| self.assertEqual(commit_session.get(p.id_string), r.id_string) |
| self.assertTrue(r.version > original_version) |
| self.assertEqual(result1.parent_id_string, proto1.parent_id_string) |
| self.assertEqual(result2.parent_id_string, result1.id_string) |
| version, changes, remaining = ( |
| self.GetChangesFromTimestamp([sync_type], original_version)) |
| self.assertEqual(3, len(changes)) |
| self.assertEqual(0, remaining) |
| self.assertEqual(original_version + 3, version) |
| self.assertEqual([result1, result2, result3], changes) |
| for c in changes: |
| self.assertTrue(c is not self.model._entries[c.id_string], |
| "GetChanges didn't make a defensive copy.") |
| self.assertTrue(result2.position_in_parent < result3.position_in_parent) |
| self.assertEqual(0, result2.position_in_parent) |
| |
| # Now update the items so that the second item is the parent of the |
| # first; with the first sandwiched between two new items (4 and 5). |
| # Do this in a new commit session, meaning we'll reference items from |
| # the first batch by their post-commit, server IDs. |
| commit_session = {} |
| old_cache_guid = my_cache_guid |
| my_cache_guid = 'A different GUID' |
| proto2b, result2b = DoCommit(original=result2, |
| parent=original_changes[-1]) |
| proto4, result4 = DoCommit(id_string='ID4', name='Four', |
| parent=result2, prev=None) |
| proto1b, result1b = DoCommit(original=result1, |
| parent=result2, prev=proto4) |
| proto5, result5 = DoCommit(id_string='ID5', name='Five', parent=result2, |
| prev=result1) |
| |
| self.assertEqual(2, len(commit_session), 'Only new items in second ' |
| 'batch should be in the session') |
| for p, r, original in [(proto2b, result2b, proto2), |
| (proto4, result4, proto4), |
| (proto1b, result1b, proto1), |
| (proto5, result5, proto5)]: |
| self.assertEqual(r.originator_client_item_id, original.id_string) |
| if original is not p: |
| self.assertEqual(r.id_string, p.id_string, |
| 'Ids should be stable after first commit') |
| self.assertEqual(r.originator_cache_guid, old_cache_guid) |
| else: |
| self.assertNotEqual(r.id_string, p.id_string) |
| self.assertEqual(r.originator_cache_guid, my_cache_guid) |
| self.assertEqual(commit_session.get(p.id_string), r.id_string) |
| self.assertTrue(r is not self.model._entries[r.id_string], |
| "Commit result didn't make a defensive copy.") |
| self.assertTrue(p is not self.model._entries[r.id_string], |
| "Commit didn't make a defensive copy.") |
| self.assertTrue(r.version > p.version) |
| version, changes, remaining = ( |
| self.GetChangesFromTimestamp([sync_type], original_version)) |
| self.assertEqual(5, len(changes)) |
| self.assertEqual(0, remaining) |
| self.assertEqual(original_version + 7, version) |
| self.assertEqual([result3, result2b, result4, result1b, result5], changes) |
| for c in changes: |
| self.assertTrue(c is not self.model._entries[c.id_string], |
| "GetChanges didn't make a defensive copy.") |
| self.assertTrue(result4.parent_id_string == |
| result1b.parent_id_string == |
| result5.parent_id_string == |
| result2b.id_string) |
| self.assertTrue(result4.position_in_parent < |
| result1b.position_in_parent < |
| result5.position_in_parent) |
| |
| def testUpdateSieve(self): |
| # from_timestamp, legacy mode |
| autofill = autofill_specifics_pb2.autofill |
| theme = theme_specifics_pb2.theme |
| msg = sync_pb2.GetUpdatesMessage() |
| msg.from_timestamp = 15412 |
| msg.requested_types.Extensions[autofill].SetInParent() |
| msg.requested_types.Extensions[theme].SetInParent() |
| |
| sieve = chromiumsync.UpdateSieve(msg) |
| self.assertEqual(sieve._state, |
| {chromiumsync.TOP_LEVEL: 15412, |
| chromiumsync.AUTOFILL: 15412, |
| chromiumsync.THEME: 15412}) |
| |
| response = sync_pb2.GetUpdatesResponse() |
| sieve.SaveProgress(15412, response) |
| self.assertEqual(0, len(response.new_progress_marker)) |
| self.assertFalse(response.HasField('new_timestamp')) |
| |
| response = sync_pb2.GetUpdatesResponse() |
| sieve.SaveProgress(15413, response) |
| self.assertEqual(0, len(response.new_progress_marker)) |
| self.assertTrue(response.HasField('new_timestamp')) |
| self.assertEqual(15413, response.new_timestamp) |
| |
| # Existing tokens |
| msg = sync_pb2.GetUpdatesMessage() |
| marker = msg.from_progress_marker.add() |
| marker.data_type_id = autofill.number |
| marker.token = '15412' |
| marker = msg.from_progress_marker.add() |
| marker.data_type_id = theme.number |
| marker.token = '15413' |
| sieve = chromiumsync.UpdateSieve(msg) |
| self.assertEqual(sieve._state, |
| {chromiumsync.TOP_LEVEL: 15412, |
| chromiumsync.AUTOFILL: 15412, |
| chromiumsync.THEME: 15413}) |
| |
| response = sync_pb2.GetUpdatesResponse() |
| sieve.SaveProgress(15413, response) |
| self.assertEqual(1, len(response.new_progress_marker)) |
| self.assertFalse(response.HasField('new_timestamp')) |
| marker = response.new_progress_marker[0] |
| self.assertEqual(marker.data_type_id, autofill.number) |
| self.assertEqual(marker.token, '15413') |
| self.assertFalse(marker.HasField('timestamp_token_for_migration')) |
| |
| # Empty tokens indicating from timestamp = 0 |
| msg = sync_pb2.GetUpdatesMessage() |
| marker = msg.from_progress_marker.add() |
| marker.data_type_id = autofill.number |
| marker.token = '412' |
| marker = msg.from_progress_marker.add() |
| marker.data_type_id = theme.number |
| marker.token = '' |
| sieve = chromiumsync.UpdateSieve(msg) |
| self.assertEqual(sieve._state, |
| {chromiumsync.TOP_LEVEL: 0, |
| chromiumsync.AUTOFILL: 412, |
| chromiumsync.THEME: 0}) |
| response = sync_pb2.GetUpdatesResponse() |
| sieve.SaveProgress(1, response) |
| self.assertEqual(1, len(response.new_progress_marker)) |
| self.assertFalse(response.HasField('new_timestamp')) |
| marker = response.new_progress_marker[0] |
| self.assertEqual(marker.data_type_id, theme.number) |
| self.assertEqual(marker.token, '1') |
| self.assertFalse(marker.HasField('timestamp_token_for_migration')) |
| |
| response = sync_pb2.GetUpdatesResponse() |
| sieve.SaveProgress(412, response) |
| self.assertEqual(1, len(response.new_progress_marker)) |
| self.assertFalse(response.HasField('new_timestamp')) |
| marker = response.new_progress_marker[0] |
| self.assertEqual(marker.data_type_id, theme.number) |
| self.assertEqual(marker.token, '412') |
| self.assertFalse(marker.HasField('timestamp_token_for_migration')) |
| |
| response = sync_pb2.GetUpdatesResponse() |
| sieve.SaveProgress(413, response) |
| self.assertEqual(2, len(response.new_progress_marker)) |
| self.assertFalse(response.HasField('new_timestamp')) |
| marker = response.new_progress_marker[0] |
| self.assertEqual(marker.data_type_id, theme.number) |
| self.assertEqual(marker.token, '413') |
| self.assertFalse(marker.HasField('timestamp_token_for_migration')) |
| marker = response.new_progress_marker[1] |
| self.assertEqual(marker.data_type_id, autofill.number) |
| self.assertEqual(marker.token, '413') |
| self.assertFalse(marker.HasField('timestamp_token_for_migration')) |
| |
| # Migration token timestamps (client gives timestamp, server returns token) |
| msg = sync_pb2.GetUpdatesMessage() |
| marker = msg.from_progress_marker.add() |
| marker.data_type_id = autofill.number |
| marker.timestamp_token_for_migration = 15213 |
| marker = msg.from_progress_marker.add() |
| marker.data_type_id = theme.number |
| marker.timestamp_token_for_migration = 15211 |
| sieve = chromiumsync.UpdateSieve(msg) |
| self.assertEqual(sieve._state, |
| {chromiumsync.TOP_LEVEL: 15211, |
| chromiumsync.AUTOFILL: 15213, |
| chromiumsync.THEME: 15211}) |
| response = sync_pb2.GetUpdatesResponse() |
| sieve.SaveProgress(16000, response) # There were updates |
| self.assertEqual(2, len(response.new_progress_marker)) |
| self.assertFalse(response.HasField('new_timestamp')) |
| marker = response.new_progress_marker[0] |
| self.assertEqual(marker.data_type_id, theme.number) |
| self.assertEqual(marker.token, '16000') |
| self.assertFalse(marker.HasField('timestamp_token_for_migration')) |
| marker = response.new_progress_marker[1] |
| self.assertEqual(marker.data_type_id, autofill.number) |
| self.assertEqual(marker.token, '16000') |
| self.assertFalse(marker.HasField('timestamp_token_for_migration')) |
| |
| msg = sync_pb2.GetUpdatesMessage() |
| marker = msg.from_progress_marker.add() |
| marker.data_type_id = autofill.number |
| marker.timestamp_token_for_migration = 3000 |
| marker = msg.from_progress_marker.add() |
| marker.data_type_id = theme.number |
| marker.timestamp_token_for_migration = 3000 |
| sieve = chromiumsync.UpdateSieve(msg) |
| self.assertEqual(sieve._state, |
| {chromiumsync.TOP_LEVEL: 3000, |
| chromiumsync.AUTOFILL: 3000, |
| chromiumsync.THEME: 3000}) |
| response = sync_pb2.GetUpdatesResponse() |
| sieve.SaveProgress(3000, response) # Already up to date |
| self.assertEqual(2, len(response.new_progress_marker)) |
| self.assertFalse(response.HasField('new_timestamp')) |
| marker = response.new_progress_marker[0] |
| self.assertEqual(marker.data_type_id, theme.number) |
| self.assertEqual(marker.token, '3000') |
| self.assertFalse(marker.HasField('timestamp_token_for_migration')) |
| marker = response.new_progress_marker[1] |
| self.assertEqual(marker.data_type_id, autofill.number) |
| self.assertEqual(marker.token, '3000') |
| self.assertFalse(marker.HasField('timestamp_token_for_migration')) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |