Home | History | Annotate | Download | only in testserver
      1 #!/usr/bin/python2.4
      2 # Copyright (c) 2010 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """Tests exercising chromiumsync and SyncDataModel."""
      7 
      8 import unittest
      9 
     10 import autofill_specifics_pb2
     11 import chromiumsync
     12 import sync_pb2
     13 import theme_specifics_pb2
     14 
     15 class SyncDataModelTest(unittest.TestCase):
     16   def setUp(self):
     17     self.model = chromiumsync.SyncDataModel()
     18 
     19   def AddToModel(self, proto):
     20     self.model._entries[proto.id_string] = proto
     21 
     22   def GetChangesFromTimestamp(self, requested_types, timestamp):
     23     message = sync_pb2.GetUpdatesMessage()
     24     message.from_timestamp = timestamp
     25     for data_type in requested_types:
     26       message.requested_types.Extensions[
     27         chromiumsync.SYNC_TYPE_TO_EXTENSION[data_type]].SetInParent()
     28     return self.model.GetChanges(chromiumsync.UpdateSieve(message))
     29 
     30   def testPermanentItemSpecs(self):
     31     specs = chromiumsync.SyncDataModel._PERMANENT_ITEM_SPECS
     32 
     33     declared_specs = set(['0'])
     34     for spec in specs:
     35       self.assertTrue(spec.parent_tag in declared_specs, 'parent tags must '
     36                       'be declared before use')
     37       declared_specs.add(spec.tag)
     38 
     39     unique_datatypes = set([x.sync_type for x in specs])
     40     self.assertEqual(unique_datatypes, set(chromiumsync.ALL_TYPES),
     41                      'Every sync datatype should have a permanent folder '
     42                      'associated with it')
     43 
     44   def testSaveEntry(self):
     45     proto = sync_pb2.SyncEntity()
     46     proto.id_string = 'abcd'
     47     proto.version = 0
     48     self.assertFalse(self.model._ItemExists(proto.id_string))
     49     self.model._SaveEntry(proto)
     50     self.assertEqual(1, proto.version)
     51     self.assertTrue(self.model._ItemExists(proto.id_string))
     52     self.model._SaveEntry(proto)
     53     self.assertEqual(2, proto.version)
     54     proto.version = 0
     55     self.assertTrue(self.model._ItemExists(proto.id_string))
     56     self.assertEqual(2, self.model._entries[proto.id_string].version)
     57 
     58   def testWritePosition(self):
     59     def MakeProto(id_string, parent, position):
     60       proto = sync_pb2.SyncEntity()
     61       proto.id_string = id_string
     62       proto.position_in_parent = position
     63       proto.parent_id_string = parent
     64       self.AddToModel(proto)
     65 
     66     MakeProto('a', 'X', 1000)
     67     MakeProto('b', 'X', 1800)
     68     MakeProto('c', 'X', 2600)
     69     MakeProto('a1', 'Z', 1007)
     70     MakeProto('a2', 'Z', 1807)
     71     MakeProto('a3', 'Z', 2607)
     72     MakeProto('s', 'Y', 10000)
     73 
     74     def AssertPositionResult(my_id, parent_id, prev_id, expected_position):
     75       entry = sync_pb2.SyncEntity()
     76       entry.id_string = my_id
     77       self.model._WritePosition(entry, parent_id, prev_id)
     78       self.assertEqual(expected_position, entry.position_in_parent)
     79       self.assertEqual(parent_id, entry.parent_id_string)
     80       self.assertFalse(entry.HasField('insert_after_item_id'))
     81 
     82     AssertPositionResult('new', 'new_parent', '', 0)
     83     AssertPositionResult('new', 'Y', '', 10000 - (2 ** 20))
     84     AssertPositionResult('new', 'Y', 's', 10000 + (2 ** 20))
     85     AssertPositionResult('s', 'Y', '', 10000)
     86     AssertPositionResult('s', 'Y', 's', 10000)
     87     AssertPositionResult('a1', 'Z', '', 1007)
     88 
     89     AssertPositionResult('new', 'X', '', 1000 - (2 ** 20))
     90     AssertPositionResult('new', 'X', 'a', 1100)
     91     AssertPositionResult('new', 'X', 'b', 1900)
     92     AssertPositionResult('new', 'X', 'c', 2600 + (2 ** 20))
     93 
     94     AssertPositionResult('a1', 'X', '', 1000 - (2 ** 20))
     95     AssertPositionResult('a1', 'X', 'a', 1100)
     96     AssertPositionResult('a1', 'X', 'b', 1900)
     97     AssertPositionResult('a1', 'X', 'c', 2600 + (2 ** 20))
     98 
     99     AssertPositionResult('a', 'X', '', 1000)
    100     AssertPositionResult('a', 'X', 'b', 1900)
    101     AssertPositionResult('a', 'X', 'c', 2600 + (2 ** 20))
    102 
    103     AssertPositionResult('b', 'X', '', 1000 - (2 ** 20))
    104     AssertPositionResult('b', 'X', 'a', 1800)
    105     AssertPositionResult('b', 'X', 'c', 2600 + (2 ** 20))
    106 
    107     AssertPositionResult('c', 'X', '', 1000 - (2 ** 20))
    108     AssertPositionResult('c', 'X', 'a', 1100)
    109     AssertPositionResult('c', 'X', 'b', 2600)
    110 
    111   def testCreatePermanentItems(self):
    112     self.model._CreatePermanentItems(chromiumsync.ALL_TYPES)
    113     self.assertEqual(len(chromiumsync.ALL_TYPES) + 2,
    114                      len(self.model._entries))
    115 
    116   def ExpectedPermanentItemCount(self, sync_type):
    117     if sync_type == chromiumsync.BOOKMARK:
    118       return 4
    119     elif sync_type == chromiumsync.TOP_LEVEL:
    120       return 1
    121     else:
    122       return 2
    123 
    124   def testGetChangesFromTimestampZeroForEachType(self):
    125     all_types = chromiumsync.ALL_TYPES[1:]
    126     for sync_type in all_types:
    127       self.model = chromiumsync.SyncDataModel()
    128       request_types = [sync_type]
    129 
    130       version, changes, remaining = (
    131           self.GetChangesFromTimestamp(request_types, 0))
    132 
    133       expected_count = self.ExpectedPermanentItemCount(sync_type)
    134       self.assertEqual(expected_count, version)
    135       self.assertEqual(expected_count, len(changes))
    136       self.assertEqual('google_chrome', changes[0].server_defined_unique_tag)
    137       for change in changes:
    138         self.assertTrue(change.HasField('server_defined_unique_tag'))
    139         self.assertEqual(change.version, change.sync_timestamp)
    140         self.assertTrue(change.version <= version)
    141 
    142       # Test idempotence: another GetUpdates from ts=0 shouldn't recreate.
    143       version, changes, remaining = (
    144           self.GetChangesFromTimestamp(request_types, 0))
    145       self.assertEqual(expected_count, version)
    146       self.assertEqual(expected_count, len(changes))
    147       self.assertEqual(0, remaining)
    148 
    149       # Doing a wider GetUpdates from timestamp zero shouldn't recreate either.
    150       new_version, changes, remaining = (
    151           self.GetChangesFromTimestamp(all_types, 0))
    152       self.assertEqual(len(chromiumsync.SyncDataModel._PERMANENT_ITEM_SPECS),
    153                        new_version)
    154       self.assertEqual(new_version, len(changes))
    155       self.assertEqual(0, remaining)
    156       version, changes, remaining = (
    157           self.GetChangesFromTimestamp(request_types, 0))
    158       self.assertEqual(new_version, version)
    159       self.assertEqual(expected_count, len(changes))
    160       self.assertEqual(0, remaining)
    161 
    162   def testBatchSize(self):
    163     for sync_type in chromiumsync.ALL_TYPES[1:]:
    164       specifics = chromiumsync.GetDefaultEntitySpecifics(sync_type)
    165       self.model = chromiumsync.SyncDataModel()
    166       request_types = [sync_type]
    167 
    168       for i in range(self.model._BATCH_SIZE*3):
    169         entry = sync_pb2.SyncEntity()
    170         entry.id_string = 'batch test %d' % i
    171         entry.specifics.CopyFrom(specifics)
    172         self.model._SaveEntry(entry)
    173       last_bit = self.ExpectedPermanentItemCount(sync_type)
    174       version, changes, changes_remaining = (
    175           self.GetChangesFromTimestamp(request_types, 0))
    176       self.assertEqual(self.model._BATCH_SIZE, version)
    177       self.assertEqual(self.model._BATCH_SIZE*2 + last_bit, changes_remaining)
    178       version, changes, changes_remaining = (
    179           self.GetChangesFromTimestamp(request_types, version))
    180       self.assertEqual(self.model._BATCH_SIZE*2, version)
    181       self.assertEqual(self.model._BATCH_SIZE + last_bit, changes_remaining)
    182       version, changes, changes_remaining = (
    183           self.GetChangesFromTimestamp(request_types, version))
    184       self.assertEqual(self.model._BATCH_SIZE*3, version)
    185       self.assertEqual(last_bit, changes_remaining)
    186       version, changes, changes_remaining = (
    187           self.GetChangesFromTimestamp(request_types, version))
    188       self.assertEqual(self.model._BATCH_SIZE*3 + last_bit, version)
    189       self.assertEqual(0, changes_remaining)
    190 
    191       # Now delete a third of the items.
    192       for i in xrange(self.model._BATCH_SIZE*3 - 1, 0, -3):
    193         entry = sync_pb2.SyncEntity()
    194         entry.id_string = 'batch test %d' % i
    195         entry.deleted = True
    196         self.model._SaveEntry(entry)
    197 
    198       # The batch counts shouldn't change.
    199       version, changes, changes_remaining = (
    200           self.GetChangesFromTimestamp(request_types, 0))
    201       self.assertEqual(self.model._BATCH_SIZE, len(changes))
    202       self.assertEqual(self.model._BATCH_SIZE*2 + last_bit, changes_remaining)
    203       version, changes, changes_remaining = (
    204           self.GetChangesFromTimestamp(request_types, version))
    205       self.assertEqual(self.model._BATCH_SIZE, len(changes))
    206       self.assertEqual(self.model._BATCH_SIZE + last_bit, changes_remaining)
    207       version, changes, changes_remaining = (
    208           self.GetChangesFromTimestamp(request_types, version))
    209       self.assertEqual(self.model._BATCH_SIZE, len(changes))
    210       self.assertEqual(last_bit, changes_remaining)
    211       version, changes, changes_remaining = (
    212           self.GetChangesFromTimestamp(request_types, version))
    213       self.assertEqual(last_bit, len(changes))
    214       self.assertEqual(self.model._BATCH_SIZE*4 + last_bit, version)
    215       self.assertEqual(0, changes_remaining)
    216 
    217   def testCommitEachDataType(self):
    218     for sync_type in chromiumsync.ALL_TYPES[1:]:
    219       specifics = chromiumsync.GetDefaultEntitySpecifics(sync_type)
    220       self.model = chromiumsync.SyncDataModel()
    221       my_cache_guid = '112358132134'
    222       parent = 'foobar'
    223       commit_session = {}
    224 
    225       # Start with a GetUpdates from timestamp 0, to populate permanent items.
    226       original_version, original_changes, changes_remaining = (
    227           self.GetChangesFromTimestamp([sync_type], 0))
    228 
    229       def DoCommit(original=None, id_string='', name=None, parent=None,
    230                    prev=None):
    231         proto = sync_pb2.SyncEntity()
    232         if original is not None:
    233           proto.version = original.version
    234           proto.id_string = original.id_string
    235           proto.parent_id_string = original.parent_id_string
    236           proto.name = original.name
    237         else:
    238           proto.id_string = id_string
    239           proto.version = 0
    240         proto.specifics.CopyFrom(specifics)
    241         if name is not None:
    242           proto.name = name
    243         if parent:
    244           proto.parent_id_string = parent.id_string
    245         if prev:
    246           proto.insert_after_item_id = prev.id_string
    247         else:
    248           proto.insert_after_item_id = ''
    249         proto.folder = True
    250         proto.deleted = False
    251         result = self.model.CommitEntry(proto, my_cache_guid, commit_session)
    252         self.assertTrue(result)
    253         return (proto, result)
    254 
    255       # Commit a new item.
    256       proto1, result1 = DoCommit(name='namae', id_string='Foo',
    257                                  parent=original_changes[-1])
    258       # Commit an item whose parent is another item (referenced via the
    259       # pre-commit ID).
    260       proto2, result2 = DoCommit(name='Secondo', id_string='Bar',
    261                                  parent=proto1)
    262         # Commit a sibling of the second item.
    263       proto3, result3 = DoCommit(name='Third!', id_string='Baz',
    264                                  parent=proto1, prev=proto2)
    265 
    266       self.assertEqual(3, len(commit_session))
    267       for p, r in [(proto1, result1), (proto2, result2), (proto3, result3)]:
    268         self.assertNotEqual(r.id_string, p.id_string)
    269         self.assertEqual(r.originator_client_item_id, p.id_string)
    270         self.assertEqual(r.originator_cache_guid, my_cache_guid)
    271         self.assertTrue(r is not self.model._entries[r.id_string],
    272                         "Commit result didn't make a defensive copy.")
    273         self.assertTrue(p is not self.model._entries[r.id_string],
    274                         "Commit result didn't make a defensive copy.")
    275         self.assertEqual(commit_session.get(p.id_string), r.id_string)
    276         self.assertTrue(r.version > original_version)
    277       self.assertEqual(result1.parent_id_string, proto1.parent_id_string)
    278       self.assertEqual(result2.parent_id_string, result1.id_string)
    279       version, changes, remaining = (
    280           self.GetChangesFromTimestamp([sync_type], original_version))
    281       self.assertEqual(3, len(changes))
    282       self.assertEqual(0, remaining)
    283       self.assertEqual(original_version + 3, version)
    284       self.assertEqual([result1, result2, result3], changes)
    285       for c in changes:
    286         self.assertTrue(c is not self.model._entries[c.id_string],
    287                         "GetChanges didn't make a defensive copy.")
    288       self.assertTrue(result2.position_in_parent < result3.position_in_parent)
    289       self.assertEqual(0, result2.position_in_parent)
    290 
    291       # Now update the items so that the second item is the parent of the
    292       # first; with the first sandwiched between two new items (4 and 5).
    293       # Do this in a new commit session, meaning we'll reference items from
    294       # the first batch by their post-commit, server IDs.
    295       commit_session = {}
    296       old_cache_guid = my_cache_guid
    297       my_cache_guid = 'A different GUID'
    298       proto2b, result2b = DoCommit(original=result2,
    299                                    parent=original_changes[-1])
    300       proto4, result4 = DoCommit(id_string='ID4', name='Four',
    301                                  parent=result2, prev=None)
    302       proto1b, result1b = DoCommit(original=result1,
    303                                    parent=result2, prev=proto4)
    304       proto5, result5 = DoCommit(id_string='ID5', name='Five', parent=result2,
    305                                  prev=result1)
    306 
    307       self.assertEqual(2, len(commit_session), 'Only new items in second '
    308                        'batch should be in the session')
    309       for p, r, original in [(proto2b, result2b, proto2),
    310                              (proto4, result4, proto4),
    311                              (proto1b, result1b, proto1),
    312                              (proto5, result5, proto5)]:
    313         self.assertEqual(r.originator_client_item_id, original.id_string)
    314         if original is not p:
    315           self.assertEqual(r.id_string, p.id_string,
    316                            'Ids should be stable after first commit')
    317           self.assertEqual(r.originator_cache_guid, old_cache_guid)
    318         else:
    319           self.assertNotEqual(r.id_string, p.id_string)
    320           self.assertEqual(r.originator_cache_guid, my_cache_guid)
    321           self.assertEqual(commit_session.get(p.id_string), r.id_string)
    322         self.assertTrue(r is not self.model._entries[r.id_string],
    323                         "Commit result didn't make a defensive copy.")
    324         self.assertTrue(p is not self.model._entries[r.id_string],
    325                         "Commit didn't make a defensive copy.")
    326         self.assertTrue(r.version > p.version)
    327       version, changes, remaining = (
    328           self.GetChangesFromTimestamp([sync_type], original_version))
    329       self.assertEqual(5, len(changes))
    330       self.assertEqual(0, remaining)
    331       self.assertEqual(original_version + 7, version)
    332       self.assertEqual([result3, result2b, result4, result1b, result5], changes)
    333       for c in changes:
    334         self.assertTrue(c is not self.model._entries[c.id_string],
    335                         "GetChanges didn't make a defensive copy.")
    336       self.assertTrue(result4.parent_id_string ==
    337                       result1b.parent_id_string ==
    338                       result5.parent_id_string ==
    339                       result2b.id_string)
    340       self.assertTrue(result4.position_in_parent <
    341                       result1b.position_in_parent <
    342                       result5.position_in_parent)
    343 
    344   def testUpdateSieve(self):
    345     # from_timestamp, legacy mode
    346     autofill = autofill_specifics_pb2.autofill
    347     theme = theme_specifics_pb2.theme
    348     msg = sync_pb2.GetUpdatesMessage()
    349     msg.from_timestamp = 15412
    350     msg.requested_types.Extensions[autofill].SetInParent()
    351     msg.requested_types.Extensions[theme].SetInParent()
    352 
    353     sieve = chromiumsync.UpdateSieve(msg)
    354     self.assertEqual(sieve._state,
    355         {chromiumsync.TOP_LEVEL: 15412,
    356          chromiumsync.AUTOFILL: 15412,
    357          chromiumsync.THEME: 15412})
    358 
    359     response = sync_pb2.GetUpdatesResponse()
    360     sieve.SaveProgress(15412, response)
    361     self.assertEqual(0, len(response.new_progress_marker))
    362     self.assertFalse(response.HasField('new_timestamp'))
    363 
    364     response = sync_pb2.GetUpdatesResponse()
    365     sieve.SaveProgress(15413, response)
    366     self.assertEqual(0, len(response.new_progress_marker))
    367     self.assertTrue(response.HasField('new_timestamp'))
    368     self.assertEqual(15413, response.new_timestamp)
    369 
    370     # Existing tokens
    371     msg = sync_pb2.GetUpdatesMessage()
    372     marker = msg.from_progress_marker.add()
    373     marker.data_type_id = autofill.number
    374     marker.token = '15412'
    375     marker = msg.from_progress_marker.add()
    376     marker.data_type_id = theme.number
    377     marker.token = '15413'
    378     sieve = chromiumsync.UpdateSieve(msg)
    379     self.assertEqual(sieve._state,
    380         {chromiumsync.TOP_LEVEL: 15412,
    381          chromiumsync.AUTOFILL: 15412,
    382          chromiumsync.THEME: 15413})
    383 
    384     response = sync_pb2.GetUpdatesResponse()
    385     sieve.SaveProgress(15413, response)
    386     self.assertEqual(1, len(response.new_progress_marker))
    387     self.assertFalse(response.HasField('new_timestamp'))
    388     marker = response.new_progress_marker[0]
    389     self.assertEqual(marker.data_type_id, autofill.number)
    390     self.assertEqual(marker.token, '15413')
    391     self.assertFalse(marker.HasField('timestamp_token_for_migration'))
    392 
    393     # Empty tokens indicating from timestamp = 0
    394     msg = sync_pb2.GetUpdatesMessage()
    395     marker = msg.from_progress_marker.add()
    396     marker.data_type_id = autofill.number
    397     marker.token = '412'
    398     marker = msg.from_progress_marker.add()
    399     marker.data_type_id = theme.number
    400     marker.token = ''
    401     sieve = chromiumsync.UpdateSieve(msg)
    402     self.assertEqual(sieve._state,
    403         {chromiumsync.TOP_LEVEL: 0,
    404          chromiumsync.AUTOFILL: 412,
    405          chromiumsync.THEME: 0})
    406     response = sync_pb2.GetUpdatesResponse()
    407     sieve.SaveProgress(1, response)
    408     self.assertEqual(1, len(response.new_progress_marker))
    409     self.assertFalse(response.HasField('new_timestamp'))
    410     marker = response.new_progress_marker[0]
    411     self.assertEqual(marker.data_type_id, theme.number)
    412     self.assertEqual(marker.token, '1')
    413     self.assertFalse(marker.HasField('timestamp_token_for_migration'))
    414 
    415     response = sync_pb2.GetUpdatesResponse()
    416     sieve.SaveProgress(412, response)
    417     self.assertEqual(1, len(response.new_progress_marker))
    418     self.assertFalse(response.HasField('new_timestamp'))
    419     marker = response.new_progress_marker[0]
    420     self.assertEqual(marker.data_type_id, theme.number)
    421     self.assertEqual(marker.token, '412')
    422     self.assertFalse(marker.HasField('timestamp_token_for_migration'))
    423 
    424     response = sync_pb2.GetUpdatesResponse()
    425     sieve.SaveProgress(413, response)
    426     self.assertEqual(2, len(response.new_progress_marker))
    427     self.assertFalse(response.HasField('new_timestamp'))
    428     marker = response.new_progress_marker[0]
    429     self.assertEqual(marker.data_type_id, theme.number)
    430     self.assertEqual(marker.token, '413')
    431     self.assertFalse(marker.HasField('timestamp_token_for_migration'))
    432     marker = response.new_progress_marker[1]
    433     self.assertEqual(marker.data_type_id, autofill.number)
    434     self.assertEqual(marker.token, '413')
    435     self.assertFalse(marker.HasField('timestamp_token_for_migration'))
    436 
    437     # Migration token timestamps (client gives timestamp, server returns token)
    438     msg = sync_pb2.GetUpdatesMessage()
    439     marker = msg.from_progress_marker.add()
    440     marker.data_type_id = autofill.number
    441     marker.timestamp_token_for_migration = 15213
    442     marker = msg.from_progress_marker.add()
    443     marker.data_type_id = theme.number
    444     marker.timestamp_token_for_migration = 15211
    445     sieve = chromiumsync.UpdateSieve(msg)
    446     self.assertEqual(sieve._state,
    447         {chromiumsync.TOP_LEVEL: 15211,
    448          chromiumsync.AUTOFILL: 15213,
    449          chromiumsync.THEME: 15211})
    450     response = sync_pb2.GetUpdatesResponse()
    451     sieve.SaveProgress(16000, response)  # There were updates
    452     self.assertEqual(2, len(response.new_progress_marker))
    453     self.assertFalse(response.HasField('new_timestamp'))
    454     marker = response.new_progress_marker[0]
    455     self.assertEqual(marker.data_type_id, theme.number)
    456     self.assertEqual(marker.token, '16000')
    457     self.assertFalse(marker.HasField('timestamp_token_for_migration'))
    458     marker = response.new_progress_marker[1]
    459     self.assertEqual(marker.data_type_id, autofill.number)
    460     self.assertEqual(marker.token, '16000')
    461     self.assertFalse(marker.HasField('timestamp_token_for_migration'))
    462 
    463     msg = sync_pb2.GetUpdatesMessage()
    464     marker = msg.from_progress_marker.add()
    465     marker.data_type_id = autofill.number
    466     marker.timestamp_token_for_migration = 3000
    467     marker = msg.from_progress_marker.add()
    468     marker.data_type_id = theme.number
    469     marker.timestamp_token_for_migration = 3000
    470     sieve = chromiumsync.UpdateSieve(msg)
    471     self.assertEqual(sieve._state,
    472         {chromiumsync.TOP_LEVEL: 3000,
    473          chromiumsync.AUTOFILL: 3000,
    474          chromiumsync.THEME: 3000})
    475     response = sync_pb2.GetUpdatesResponse()
    476     sieve.SaveProgress(3000, response)  # Already up to date
    477     self.assertEqual(2, len(response.new_progress_marker))
    478     self.assertFalse(response.HasField('new_timestamp'))
    479     marker = response.new_progress_marker[0]
    480     self.assertEqual(marker.data_type_id, theme.number)
    481     self.assertEqual(marker.token, '3000')
    482     self.assertFalse(marker.HasField('timestamp_token_for_migration'))
    483     marker = response.new_progress_marker[1]
    484     self.assertEqual(marker.data_type_id, autofill.number)
    485     self.assertEqual(marker.token, '3000')
    486     self.assertFalse(marker.HasField('timestamp_token_for_migration'))
    487 
    488 
    489 if __name__ == '__main__':
    490   unittest.main()
    491