#!/usr/bin/env python # Copyright (c) 2012 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 pickle import unittest import autofill_specifics_pb2 import bookmark_specifics_pb2 import chromiumsync import sync_pb2 import theme_specifics_pb2 class SyncDataModelTest(unittest.TestCase): def setUp(self): self.model = chromiumsync.SyncDataModel() # The Synced Bookmarks folder is not created by default self._expect_synced_bookmarks_folder = False 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: getattr(message.requested_types, chromiumsync.SYNC_TYPE_TO_DESCRIPTOR[ data_type].name).SetInParent() return self.model.GetChanges( chromiumsync.UpdateSieve(message, self.model.migration_history)) def FindMarkerByNumber(self, markers, datatype): """Search a list of progress markers and find the one for a datatype.""" for marker in markers: if marker.data_type_id == datatype.number: return marker self.fail('Required marker not found: %s' % datatype.name) 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[1:]), '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 testCreatePermanentItems(self): self.model._CreateDefaultPermanentItems(chromiumsync.ALL_TYPES) self.assertEqual(len(chromiumsync.ALL_TYPES) + 1, len(self.model._entries)) def ExpectedPermanentItemCount(self, sync_type): if sync_type == chromiumsync.BOOKMARK: if self._expect_synced_bookmarks_folder: return 4 else: return 3 else: return 1 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)) 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)) if self._expect_synced_bookmarks_folder: self.assertEqual(len(chromiumsync.SyncDataModel._PERMANENT_ITEM_SPECS), new_version) else: self.assertEqual( len(chromiumsync.SyncDataModel._PERMANENT_ITEM_SPECS) -1, 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, position=0): 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 proto.insert_after_item_id = 'please discard' proto.position_in_parent = position 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], position=100) # Commit an item whose parent is another item (referenced via the # pre-commit ID). proto2, result2 = DoCommit(name='Secondo', id_string='Bar', parent=proto1, position=-100) # Commit a sibling of the second item. proto3, result3 = DoCommit(name='Third!', id_string='Baz', parent=proto1, position=-50) 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(-100, 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, position=-200) proto1b, result1b = DoCommit(original=result1, parent=result2, position=-150) proto5, result5 = DoCommit(id_string='ID5', name='Five', parent=result2, position=150) 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 = chromiumsync.SYNC_TYPE_FIELDS['autofill'] theme = chromiumsync.SYNC_TYPE_FIELDS['theme'] msg = sync_pb2.GetUpdatesMessage() msg.from_timestamp = 15412 msg.requested_types.autofill.SetInParent() msg.requested_types.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 = pickle.dumps((15412, 1)) marker = msg.from_progress_marker.add() marker.data_type_id = theme.number marker.token = pickle.dumps((15413, 1)) 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(pickle.loads(marker.token), (15413, 1)) 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 = pickle.dumps((412, 1)) 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(pickle.loads(marker.token), (1, 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(pickle.loads(marker.token), (412, 1)) 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 = self.FindMarkerByNumber(response.new_progress_marker, theme) self.assertEqual(pickle.loads(marker.token), (413, 1)) self.assertFalse(marker.HasField('timestamp_token_for_migration')) marker = self.FindMarkerByNumber(response.new_progress_marker, autofill) self.assertEqual(pickle.loads(marker.token), (413, 1)) self.assertFalse(marker.HasField('timestamp_token_for_migration')) # Migration token timestamps (client gives timestamp, server returns token) # These are for migrating from the old 'timestamp' protocol to the # progressmarker protocol, and have nothing to do with the MIGRATION_DONE # error code. 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 = self.FindMarkerByNumber(response.new_progress_marker, theme) self.assertEqual(pickle.loads(marker.token), (16000, 1)) self.assertFalse(marker.HasField('timestamp_token_for_migration')) marker = self.FindMarkerByNumber(response.new_progress_marker, autofill) self.assertEqual(pickle.loads(marker.token), (16000, 1)) 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 = self.FindMarkerByNumber(response.new_progress_marker, theme) self.assertEqual(pickle.loads(marker.token), (3000, 1)) self.assertFalse(marker.HasField('timestamp_token_for_migration')) marker = self.FindMarkerByNumber(response.new_progress_marker, autofill) self.assertEqual(pickle.loads(marker.token), (3000, 1)) self.assertFalse(marker.HasField('timestamp_token_for_migration')) def testCheckRaiseTransientError(self): testserver = chromiumsync.TestServer() http_code, raw_respon = testserver.HandleSetTransientError() self.assertEqual(http_code, 200) try: testserver.CheckTransientError() self.fail('Should have raised transient error exception') except chromiumsync.TransientError: self.assertTrue(testserver.transient_error) def testUpdateSieveStoreMigration(self): autofill = chromiumsync.SYNC_TYPE_FIELDS['autofill'] theme = chromiumsync.SYNC_TYPE_FIELDS['theme'] migrator = chromiumsync.MigrationHistory() msg = sync_pb2.GetUpdatesMessage() marker = msg.from_progress_marker.add() marker.data_type_id = autofill.number marker.token = pickle.dumps((15412, 1)) marker = msg.from_progress_marker.add() marker.data_type_id = theme.number marker.token = pickle.dumps((15413, 1)) sieve = chromiumsync.UpdateSieve(msg, migrator) sieve.CheckMigrationState() migrator.Bump([chromiumsync.BOOKMARK, chromiumsync.PASSWORD]) # v=2 sieve = chromiumsync.UpdateSieve(msg, migrator) sieve.CheckMigrationState() self.assertEqual(sieve._state, {chromiumsync.TOP_LEVEL: 15412, chromiumsync.AUTOFILL: 15412, chromiumsync.THEME: 15413}) migrator.Bump([chromiumsync.AUTOFILL, chromiumsync.PASSWORD]) # v=3 sieve = chromiumsync.UpdateSieve(msg, migrator) try: sieve.CheckMigrationState() self.fail('Should have raised.') except chromiumsync.MigrationDoneError, error: # We want this to happen. self.assertEqual([chromiumsync.AUTOFILL], error.datatypes) msg = sync_pb2.GetUpdatesMessage() marker = msg.from_progress_marker.add() marker.data_type_id = autofill.number marker.token = '' marker = msg.from_progress_marker.add() marker.data_type_id = theme.number marker.token = pickle.dumps((15413, 1)) sieve = chromiumsync.UpdateSieve(msg, migrator) sieve.CheckMigrationState() response = sync_pb2.GetUpdatesResponse() sieve.SaveProgress(15412, response) # There were updates self.assertEqual(1, len(response.new_progress_marker)) self.assertFalse(response.HasField('new_timestamp')) self.assertFalse(marker.HasField('timestamp_token_for_migration')) marker = self.FindMarkerByNumber(response.new_progress_marker, autofill) self.assertEqual(pickle.loads(marker.token), (15412, 3)) 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.token = pickle.dumps((15412, 3)) marker = msg.from_progress_marker.add() marker.data_type_id = theme.number marker.token = pickle.dumps((15413, 1)) sieve = chromiumsync.UpdateSieve(msg, migrator) sieve.CheckMigrationState() migrator.Bump([chromiumsync.THEME, chromiumsync.AUTOFILL]) # v=4 migrator.Bump([chromiumsync.AUTOFILL]) # v=5 sieve = chromiumsync.UpdateSieve(msg, migrator) try: sieve.CheckMigrationState() self.fail("Should have raised.") except chromiumsync.MigrationDoneError, error: # We want this to happen. self.assertEqual(set([chromiumsync.THEME, chromiumsync.AUTOFILL]), set(error.datatypes)) msg = sync_pb2.GetUpdatesMessage() marker = msg.from_progress_marker.add() marker.data_type_id = autofill.number marker.token = '' marker = msg.from_progress_marker.add() marker.data_type_id = theme.number marker.token = pickle.dumps((15413, 1)) sieve = chromiumsync.UpdateSieve(msg, migrator) try: sieve.CheckMigrationState() self.fail("Should have raised.") except chromiumsync.MigrationDoneError, error: # We want this to happen. self.assertEqual([chromiumsync.THEME], error.datatypes) msg = sync_pb2.GetUpdatesMessage() marker = msg.from_progress_marker.add() marker.data_type_id = autofill.number marker.token = '' marker = msg.from_progress_marker.add() marker.data_type_id = theme.number marker.token = '' sieve = chromiumsync.UpdateSieve(msg, migrator) sieve.CheckMigrationState() response = sync_pb2.GetUpdatesResponse() sieve.SaveProgress(15412, response) # There were updates self.assertEqual(2, len(response.new_progress_marker)) self.assertFalse(response.HasField('new_timestamp')) self.assertFalse(marker.HasField('timestamp_token_for_migration')) marker = self.FindMarkerByNumber(response.new_progress_marker, autofill) self.assertEqual(pickle.loads(marker.token), (15412, 5)) self.assertFalse(marker.HasField('timestamp_token_for_migration')) marker = self.FindMarkerByNumber(response.new_progress_marker, theme) self.assertEqual(pickle.loads(marker.token), (15412, 4)) 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.token = pickle.dumps((15412, 5)) marker = msg.from_progress_marker.add() marker.data_type_id = theme.number marker.token = pickle.dumps((15413, 4)) sieve = chromiumsync.UpdateSieve(msg, migrator) sieve.CheckMigrationState() def testCreateSyncedBookmaks(self): version1, changes, remaining = ( self.GetChangesFromTimestamp([chromiumsync.BOOKMARK], 0)) id_string = self.model._MakeCurrentId(chromiumsync.BOOKMARK, 'synced_bookmarks') self.assertFalse(self.model._ItemExists(id_string)) self._expect_synced_bookmarks_folder = True self.model.TriggerCreateSyncedBookmarks() self.assertTrue(self.model._ItemExists(id_string)) # Check that the version changed when the folder was created and the only # change was the folder creation. version2, changes, remaining = ( self.GetChangesFromTimestamp([chromiumsync.BOOKMARK], version1)) self.assertEqual(len(changes), 1) self.assertEqual(changes[0].id_string, id_string) self.assertNotEqual(version1, version2) self.assertEqual( self.ExpectedPermanentItemCount(chromiumsync.BOOKMARK), version2) # Ensure getting from timestamp 0 includes the folder. version, changes, remaining = ( self.GetChangesFromTimestamp([chromiumsync.BOOKMARK], 0)) self.assertEqual( self.ExpectedPermanentItemCount(chromiumsync.BOOKMARK), len(changes)) self.assertEqual(version2, version) def testGetKey(self): key1 = self.model.GetKey() key2 = self.model.GetKey() self.assertTrue(len(key1) > 0) self.assertEqual(key1, key2) def testTriggerEnableKeystoreEncryption(self): version1, changes, remaining = ( self.GetChangesFromTimestamp([chromiumsync.EXPERIMENTS], 0)) keystore_encryption_id_string = ( self.model._ClientTagToId( chromiumsync.EXPERIMENTS, chromiumsync.KEYSTORE_ENCRYPTION_EXPERIMENT_TAG)) self.assertFalse(self.model._ItemExists(keystore_encryption_id_string)) self.model.TriggerEnableKeystoreEncryption() self.assertTrue(self.model._ItemExists(keystore_encryption_id_string)) # The creation of the experiment should be downloaded on the next # GetUpdates. version2, changes, remaining = ( self.GetChangesFromTimestamp([chromiumsync.EXPERIMENTS], version1)) self.assertEqual(len(changes), 1) self.assertEqual(changes[0].id_string, keystore_encryption_id_string) self.assertNotEqual(version1, version2) # Verify the experiment was created properly and is enabled. self.assertEqual(chromiumsync.KEYSTORE_ENCRYPTION_EXPERIMENT_TAG, changes[0].client_defined_unique_tag) self.assertTrue(changes[0].HasField("specifics")) self.assertTrue(changes[0].specifics.HasField("experiments")) self.assertTrue( changes[0].specifics.experiments.HasField("keystore_encryption")) self.assertTrue( changes[0].specifics.experiments.keystore_encryption.enabled) if __name__ == '__main__': unittest.main()