OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "sync/syncable/directory_unittest.h" | |
6 | |
7 #include <stddef.h> | |
8 #include <stdint.h> | |
9 | |
10 #include <cstdlib> | |
11 | |
12 #include "base/macros.h" | |
13 #include "base/rand_util.h" | |
14 #include "base/run_loop.h" | |
15 #include "base/strings/stringprintf.h" | |
16 #include "base/test/values_test_util.h" | |
17 #include "sync/internal_api/public/base/attachment_id_proto.h" | |
18 #include "sync/syncable/syncable_proto_util.h" | |
19 #include "sync/syncable/syncable_util.h" | |
20 #include "sync/syncable/syncable_write_transaction.h" | |
21 #include "sync/test/engine/test_syncable_utils.h" | |
22 #include "sync/test/test_directory_backing_store.h" | |
23 #include "sync/util/mock_unrecoverable_error_handler.h" | |
24 | |
25 using base::ExpectDictBooleanValue; | |
26 using base::ExpectDictStringValue; | |
27 | |
28 namespace syncer { | |
29 | |
30 namespace syncable { | |
31 | |
32 namespace { | |
33 | |
34 bool IsLegalNewParent(const Entry& a, const Entry& b) { | |
35 return IsLegalNewParent(a.trans(), a.GetId(), b.GetId()); | |
36 } | |
37 | |
38 void PutDataAsBookmarkFavicon(WriteTransaction* wtrans, | |
39 MutableEntry* e, | |
40 const char* bytes, | |
41 size_t bytes_length) { | |
42 sync_pb::EntitySpecifics specifics; | |
43 specifics.mutable_bookmark()->set_url("http://demo/"); | |
44 specifics.mutable_bookmark()->set_favicon(bytes, bytes_length); | |
45 e->PutSpecifics(specifics); | |
46 } | |
47 | |
48 void ExpectDataFromBookmarkFaviconEquals(BaseTransaction* trans, | |
49 Entry* e, | |
50 const char* bytes, | |
51 size_t bytes_length) { | |
52 ASSERT_TRUE(e->good()); | |
53 ASSERT_TRUE(e->GetSpecifics().has_bookmark()); | |
54 ASSERT_EQ("http://demo/", e->GetSpecifics().bookmark().url()); | |
55 ASSERT_EQ(std::string(bytes, bytes_length), | |
56 e->GetSpecifics().bookmark().favicon()); | |
57 } | |
58 | |
59 } // namespace | |
60 | |
61 const char SyncableDirectoryTest::kDirectoryName[] = "Foo"; | |
62 | |
63 SyncableDirectoryTest::SyncableDirectoryTest() { | |
64 } | |
65 | |
66 SyncableDirectoryTest::~SyncableDirectoryTest() { | |
67 } | |
68 | |
69 void SyncableDirectoryTest::SetUp() { | |
70 ASSERT_TRUE(connection_.OpenInMemory()); | |
71 ASSERT_EQ(OPENED, ReopenDirectory()); | |
72 } | |
73 | |
74 void SyncableDirectoryTest::TearDown() { | |
75 if (dir_) | |
76 dir_->SaveChanges(); | |
77 dir_.reset(); | |
78 } | |
79 | |
80 DirOpenResult SyncableDirectoryTest::ReopenDirectory() { | |
81 // Use a TestDirectoryBackingStore and sql::Connection so we can have test | |
82 // data persist across Directory object lifetimes while getting the | |
83 // performance benefits of not writing to disk. | |
84 dir_.reset(new Directory( | |
85 new TestDirectoryBackingStore(kDirectoryName, &connection_), | |
86 MakeWeakHandle(handler_.GetWeakPtr()), base::Closure(), NULL, NULL)); | |
87 | |
88 DirOpenResult open_result = | |
89 dir_->Open(kDirectoryName, &delegate_, NullTransactionObserver()); | |
90 | |
91 if (open_result != OPENED) { | |
92 dir_.reset(); | |
93 } | |
94 | |
95 return open_result; | |
96 } | |
97 | |
98 // Creates an empty entry and sets the ID field to a default one. | |
99 void SyncableDirectoryTest::CreateEntry(const ModelType& model_type, | |
100 const std::string& entryname) { | |
101 CreateEntry(model_type, entryname, TestIdFactory::FromNumber(-99)); | |
102 } | |
103 | |
104 // Creates an empty entry and sets the ID field to id. | |
105 void SyncableDirectoryTest::CreateEntry(const ModelType& model_type, | |
106 const std::string& entryname, | |
107 const int id) { | |
108 CreateEntry(model_type, entryname, TestIdFactory::FromNumber(id)); | |
109 } | |
110 | |
111 void SyncableDirectoryTest::CreateEntry(const ModelType& model_type, | |
112 const std::string& entryname, | |
113 const Id& id) { | |
114 CreateEntryWithAttachmentMetadata( | |
115 model_type, entryname, id, sync_pb::AttachmentMetadata()); | |
116 } | |
117 | |
118 void SyncableDirectoryTest::CreateEntryWithAttachmentMetadata( | |
119 const ModelType& model_type, | |
120 const std::string& entryname, | |
121 const Id& id, | |
122 const sync_pb::AttachmentMetadata& attachment_metadata) { | |
123 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get()); | |
124 MutableEntry me(&wtrans, CREATE, model_type, wtrans.root_id(), entryname); | |
125 ASSERT_TRUE(me.good()); | |
126 me.PutId(id); | |
127 me.PutAttachmentMetadata(attachment_metadata); | |
128 me.PutIsUnsynced(true); | |
129 } | |
130 | |
131 void SyncableDirectoryTest::DeleteEntry(const Id& id) { | |
132 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
133 MutableEntry entry(&trans, GET_BY_ID, id); | |
134 ASSERT_TRUE(entry.good()); | |
135 entry.PutIsDel(true); | |
136 } | |
137 | |
138 DirOpenResult SyncableDirectoryTest::SimulateSaveAndReloadDir() { | |
139 if (!dir_->SaveChanges()) | |
140 return FAILED_IN_UNITTEST; | |
141 | |
142 return ReopenDirectory(); | |
143 } | |
144 | |
145 DirOpenResult SyncableDirectoryTest::SimulateCrashAndReloadDir() { | |
146 return ReopenDirectory(); | |
147 } | |
148 | |
149 void SyncableDirectoryTest::GetAllMetaHandles(BaseTransaction* trans, | |
150 MetahandleSet* result) { | |
151 dir_->GetAllMetaHandles(trans, result); | |
152 } | |
153 | |
154 void SyncableDirectoryTest::CheckPurgeEntriesWithTypeInSucceeded( | |
155 ModelTypeSet types_to_purge, | |
156 bool before_reload) { | |
157 SCOPED_TRACE(testing::Message("Before reload: ") << before_reload); | |
158 { | |
159 ReadTransaction trans(FROM_HERE, dir_.get()); | |
160 MetahandleSet all_set; | |
161 dir_->GetAllMetaHandles(&trans, &all_set); | |
162 EXPECT_EQ(4U, all_set.size()); | |
163 if (before_reload) | |
164 EXPECT_EQ(6U, dir_->kernel()->metahandles_to_purge.size()); | |
165 for (MetahandleSet::iterator iter = all_set.begin(); iter != all_set.end(); | |
166 ++iter) { | |
167 Entry e(&trans, GET_BY_HANDLE, *iter); | |
168 const ModelType local_type = e.GetModelType(); | |
169 const ModelType server_type = e.GetServerModelType(); | |
170 | |
171 // Note the dance around incrementing |it|, since we sometimes erase(). | |
172 if ((IsRealDataType(local_type) && types_to_purge.Has(local_type)) || | |
173 (IsRealDataType(server_type) && types_to_purge.Has(server_type))) { | |
174 FAIL() << "Illegal type should have been deleted."; | |
175 } | |
176 } | |
177 } | |
178 | |
179 for (ModelTypeSet::Iterator it = types_to_purge.First(); it.Good(); | |
180 it.Inc()) { | |
181 EXPECT_FALSE(dir_->InitialSyncEndedForType(it.Get())); | |
182 sync_pb::DataTypeProgressMarker progress; | |
183 dir_->GetDownloadProgress(it.Get(), &progress); | |
184 EXPECT_EQ("", progress.token()); | |
185 | |
186 ReadTransaction trans(FROM_HERE, dir_.get()); | |
187 sync_pb::DataTypeContext context; | |
188 dir_->GetDataTypeContext(&trans, it.Get(), &context); | |
189 EXPECT_TRUE(context.SerializeAsString().empty()); | |
190 } | |
191 EXPECT_FALSE(types_to_purge.Has(BOOKMARKS)); | |
192 EXPECT_TRUE(dir_->InitialSyncEndedForType(BOOKMARKS)); | |
193 } | |
194 | |
195 bool SyncableDirectoryTest::IsInDirtyMetahandles(int64_t metahandle) { | |
196 return 1 == dir_->kernel()->dirty_metahandles.count(metahandle); | |
197 } | |
198 | |
199 bool SyncableDirectoryTest::IsInMetahandlesToPurge(int64_t metahandle) { | |
200 return 1 == dir_->kernel()->metahandles_to_purge.count(metahandle); | |
201 } | |
202 | |
203 std::unique_ptr<Directory>& SyncableDirectoryTest::dir() { | |
204 return dir_; | |
205 } | |
206 | |
207 DirectoryChangeDelegate* SyncableDirectoryTest::directory_change_delegate() { | |
208 return &delegate_; | |
209 } | |
210 | |
211 Encryptor* SyncableDirectoryTest::encryptor() { | |
212 return &encryptor_; | |
213 } | |
214 | |
215 | |
216 TestUnrecoverableErrorHandler* | |
217 SyncableDirectoryTest::unrecoverable_error_handler() { | |
218 return &handler_; | |
219 } | |
220 | |
221 void SyncableDirectoryTest::ValidateEntry(BaseTransaction* trans, | |
222 int64_t id, | |
223 bool check_name, | |
224 const std::string& name, | |
225 int64_t base_version, | |
226 int64_t server_version, | |
227 bool is_del) { | |
228 Entry e(trans, GET_BY_ID, TestIdFactory::FromNumber(id)); | |
229 ASSERT_TRUE(e.good()); | |
230 if (check_name) | |
231 ASSERT_TRUE(name == e.GetNonUniqueName()); | |
232 ASSERT_TRUE(base_version == e.GetBaseVersion()); | |
233 ASSERT_TRUE(server_version == e.GetServerVersion()); | |
234 ASSERT_TRUE(is_del == e.GetIsDel()); | |
235 } | |
236 | |
237 TEST_F(SyncableDirectoryTest, TakeSnapshotGetsMetahandlesToPurge) { | |
238 const int metas_to_create = 50; | |
239 MetahandleSet expected_purges; | |
240 MetahandleSet all_handles; | |
241 { | |
242 dir()->SetDownloadProgress(BOOKMARKS, BuildProgress(BOOKMARKS)); | |
243 dir()->SetDownloadProgress(PREFERENCES, BuildProgress(PREFERENCES)); | |
244 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
245 for (int i = 0; i < metas_to_create; i++) { | |
246 MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo"); | |
247 e.PutIsUnsynced(true); | |
248 sync_pb::EntitySpecifics specs; | |
249 if (i % 2 == 0) { | |
250 AddDefaultFieldValue(BOOKMARKS, &specs); | |
251 expected_purges.insert(e.GetMetahandle()); | |
252 all_handles.insert(e.GetMetahandle()); | |
253 } else { | |
254 AddDefaultFieldValue(PREFERENCES, &specs); | |
255 all_handles.insert(e.GetMetahandle()); | |
256 } | |
257 e.PutSpecifics(specs); | |
258 e.PutServerSpecifics(specs); | |
259 } | |
260 } | |
261 | |
262 ModelTypeSet to_purge(BOOKMARKS); | |
263 dir()->PurgeEntriesWithTypeIn(to_purge, ModelTypeSet(), ModelTypeSet()); | |
264 | |
265 Directory::SaveChangesSnapshot snapshot1; | |
266 base::AutoLock scoped_lock(dir()->kernel()->save_changes_mutex); | |
267 dir()->TakeSnapshotForSaveChanges(&snapshot1); | |
268 EXPECT_TRUE(expected_purges == snapshot1.metahandles_to_purge); | |
269 | |
270 to_purge.Clear(); | |
271 to_purge.Put(PREFERENCES); | |
272 dir()->PurgeEntriesWithTypeIn(to_purge, ModelTypeSet(), ModelTypeSet()); | |
273 | |
274 dir()->HandleSaveChangesFailure(snapshot1); | |
275 | |
276 Directory::SaveChangesSnapshot snapshot2; | |
277 dir()->TakeSnapshotForSaveChanges(&snapshot2); | |
278 EXPECT_TRUE(all_handles == snapshot2.metahandles_to_purge); | |
279 } | |
280 | |
281 TEST_F(SyncableDirectoryTest, TakeSnapshotGetsAllDirtyHandlesTest) { | |
282 const int metahandles_to_create = 100; | |
283 std::vector<int64_t> expected_dirty_metahandles; | |
284 { | |
285 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
286 for (int i = 0; i < metahandles_to_create; i++) { | |
287 MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo"); | |
288 expected_dirty_metahandles.push_back(e.GetMetahandle()); | |
289 e.PutIsUnsynced(true); | |
290 } | |
291 } | |
292 // Fake SaveChanges() and make sure we got what we expected. | |
293 { | |
294 Directory::SaveChangesSnapshot snapshot; | |
295 base::AutoLock scoped_lock(dir()->kernel()->save_changes_mutex); | |
296 dir()->TakeSnapshotForSaveChanges(&snapshot); | |
297 // Make sure there's an entry for each new metahandle. Make sure all | |
298 // entries are marked dirty. | |
299 ASSERT_EQ(expected_dirty_metahandles.size(), snapshot.dirty_metas.size()); | |
300 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin(); | |
301 i != snapshot.dirty_metas.end(); | |
302 ++i) { | |
303 ASSERT_TRUE((*i)->is_dirty()); | |
304 } | |
305 dir()->VacuumAfterSaveChanges(snapshot); | |
306 } | |
307 // Put a new value with existing transactions as well as adding new ones. | |
308 { | |
309 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
310 std::vector<int64_t> new_dirty_metahandles; | |
311 for (std::vector<int64_t>::const_iterator i = | |
312 expected_dirty_metahandles.begin(); | |
313 i != expected_dirty_metahandles.end(); ++i) { | |
314 // Change existing entries to directories to dirty them. | |
315 MutableEntry e1(&trans, GET_BY_HANDLE, *i); | |
316 e1.PutIsDir(true); | |
317 e1.PutIsUnsynced(true); | |
318 // Add new entries | |
319 MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), "bar"); | |
320 e2.PutIsUnsynced(true); | |
321 new_dirty_metahandles.push_back(e2.GetMetahandle()); | |
322 } | |
323 expected_dirty_metahandles.insert(expected_dirty_metahandles.end(), | |
324 new_dirty_metahandles.begin(), | |
325 new_dirty_metahandles.end()); | |
326 } | |
327 // Fake SaveChanges() and make sure we got what we expected. | |
328 { | |
329 Directory::SaveChangesSnapshot snapshot; | |
330 base::AutoLock scoped_lock(dir()->kernel()->save_changes_mutex); | |
331 dir()->TakeSnapshotForSaveChanges(&snapshot); | |
332 // Make sure there's an entry for each new metahandle. Make sure all | |
333 // entries are marked dirty. | |
334 EXPECT_EQ(expected_dirty_metahandles.size(), snapshot.dirty_metas.size()); | |
335 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin(); | |
336 i != snapshot.dirty_metas.end(); | |
337 ++i) { | |
338 EXPECT_TRUE((*i)->is_dirty()); | |
339 } | |
340 dir()->VacuumAfterSaveChanges(snapshot); | |
341 } | |
342 } | |
343 | |
344 TEST_F(SyncableDirectoryTest, TakeSnapshotGetsOnlyDirtyHandlesTest) { | |
345 const int metahandles_to_create = 100; | |
346 | |
347 // half of 2 * metahandles_to_create | |
348 const unsigned int number_changed = 100u; | |
349 std::vector<int64_t> expected_dirty_metahandles; | |
350 { | |
351 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
352 for (int i = 0; i < metahandles_to_create; i++) { | |
353 MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo"); | |
354 expected_dirty_metahandles.push_back(e.GetMetahandle()); | |
355 e.PutIsUnsynced(true); | |
356 } | |
357 } | |
358 dir()->SaveChanges(); | |
359 // Put a new value with existing transactions as well as adding new ones. | |
360 { | |
361 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
362 std::vector<int64_t> new_dirty_metahandles; | |
363 for (std::vector<int64_t>::const_iterator i = | |
364 expected_dirty_metahandles.begin(); | |
365 i != expected_dirty_metahandles.end(); ++i) { | |
366 // Change existing entries to directories to dirty them. | |
367 MutableEntry e1(&trans, GET_BY_HANDLE, *i); | |
368 ASSERT_TRUE(e1.good()); | |
369 e1.PutIsDir(true); | |
370 e1.PutIsUnsynced(true); | |
371 // Add new entries | |
372 MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), "bar"); | |
373 e2.PutIsUnsynced(true); | |
374 new_dirty_metahandles.push_back(e2.GetMetahandle()); | |
375 } | |
376 expected_dirty_metahandles.insert(expected_dirty_metahandles.end(), | |
377 new_dirty_metahandles.begin(), | |
378 new_dirty_metahandles.end()); | |
379 } | |
380 dir()->SaveChanges(); | |
381 // Don't make any changes whatsoever and ensure nothing comes back. | |
382 { | |
383 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
384 for (std::vector<int64_t>::const_iterator i = | |
385 expected_dirty_metahandles.begin(); | |
386 i != expected_dirty_metahandles.end(); ++i) { | |
387 MutableEntry e(&trans, GET_BY_HANDLE, *i); | |
388 ASSERT_TRUE(e.good()); | |
389 // We aren't doing anything to dirty these entries. | |
390 } | |
391 } | |
392 // Fake SaveChanges() and make sure we got what we expected. | |
393 { | |
394 Directory::SaveChangesSnapshot snapshot; | |
395 base::AutoLock scoped_lock(dir()->kernel()->save_changes_mutex); | |
396 dir()->TakeSnapshotForSaveChanges(&snapshot); | |
397 // Make sure there are no dirty_metahandles. | |
398 EXPECT_EQ(0u, snapshot.dirty_metas.size()); | |
399 dir()->VacuumAfterSaveChanges(snapshot); | |
400 } | |
401 { | |
402 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
403 bool should_change = false; | |
404 for (std::vector<int64_t>::const_iterator i = | |
405 expected_dirty_metahandles.begin(); | |
406 i != expected_dirty_metahandles.end(); ++i) { | |
407 // Maybe change entries by flipping IS_DIR. | |
408 MutableEntry e(&trans, GET_BY_HANDLE, *i); | |
409 ASSERT_TRUE(e.good()); | |
410 should_change = !should_change; | |
411 if (should_change) { | |
412 bool not_dir = !e.GetIsDir(); | |
413 e.PutIsDir(not_dir); | |
414 e.PutIsUnsynced(true); | |
415 } | |
416 } | |
417 } | |
418 // Fake SaveChanges() and make sure we got what we expected. | |
419 { | |
420 Directory::SaveChangesSnapshot snapshot; | |
421 base::AutoLock scoped_lock(dir()->kernel()->save_changes_mutex); | |
422 dir()->TakeSnapshotForSaveChanges(&snapshot); | |
423 // Make sure there's an entry for each changed metahandle. Make sure all | |
424 // entries are marked dirty. | |
425 EXPECT_EQ(number_changed, snapshot.dirty_metas.size()); | |
426 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin(); | |
427 i != snapshot.dirty_metas.end(); | |
428 ++i) { | |
429 EXPECT_TRUE((*i)->is_dirty()); | |
430 } | |
431 dir()->VacuumAfterSaveChanges(snapshot); | |
432 } | |
433 } | |
434 | |
435 // Test delete journals management. | |
436 TEST_F(SyncableDirectoryTest, ManageDeleteJournals) { | |
437 sync_pb::EntitySpecifics bookmark_specifics; | |
438 AddDefaultFieldValue(BOOKMARKS, &bookmark_specifics); | |
439 bookmark_specifics.mutable_bookmark()->set_url("url"); | |
440 | |
441 // The first two IDs are server IDs. | |
442 Id id1 = TestIdFactory::FromNumber(1); | |
443 Id id2 = TestIdFactory::FromNumber(2); | |
444 // The third one is a client ID. | |
445 Id id3 = TestIdFactory::FromNumber(-3); | |
446 int64_t handle1 = 0; | |
447 int64_t handle2 = 0; | |
448 int64_t handle3 = 0; | |
449 { | |
450 // Create 3 bookmark entries and save in database. | |
451 { | |
452 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
453 | |
454 MutableEntry item1(&trans, CREATE, BOOKMARKS, trans.root_id(), "item1"); | |
455 item1.PutId(id1); | |
456 item1.PutSpecifics(bookmark_specifics); | |
457 item1.PutServerSpecifics(bookmark_specifics); | |
458 item1.PutIsUnappliedUpdate(true); | |
459 item1.PutBaseVersion(10); | |
460 handle1 = item1.GetMetahandle(); | |
461 | |
462 MutableEntry item2(&trans, CREATE, BOOKMARKS, trans.root_id(), "item2"); | |
463 item2.PutId(id2); | |
464 item2.PutSpecifics(bookmark_specifics); | |
465 item2.PutServerSpecifics(bookmark_specifics); | |
466 item2.PutIsUnappliedUpdate(true); | |
467 item2.PutBaseVersion(10); | |
468 handle2 = item2.GetMetahandle(); | |
469 | |
470 MutableEntry item3(&trans, CREATE, BOOKMARKS, trans.root_id(), "item3"); | |
471 item3.PutId(id3); | |
472 item3.PutSpecifics(bookmark_specifics); | |
473 item3.PutServerSpecifics(bookmark_specifics); | |
474 item3.PutIsUnsynced(true); | |
475 handle3 = item3.GetMetahandle(); | |
476 } | |
477 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); | |
478 } | |
479 | |
480 { // Test adding and saving delete journals. | |
481 DeleteJournal* delete_journal = dir()->delete_journal(); | |
482 { | |
483 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
484 EntryKernelSet journal_entries; | |
485 delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries); | |
486 ASSERT_EQ(0u, journal_entries.size()); | |
487 | |
488 // Set SERVER_IS_DEL of the entries to true and they should be added to | |
489 // delete journals, but only if the deletion is initiated in update e.g. | |
490 // IS_UNAPPLIED_UPDATE is also true. | |
491 MutableEntry item1(&trans, GET_BY_ID, id1); | |
492 ASSERT_TRUE(item1.good()); | |
493 item1.PutServerIsDel(true); | |
494 MutableEntry item2(&trans, GET_BY_ID, id2); | |
495 ASSERT_TRUE(item2.good()); | |
496 item2.PutServerIsDel(true); | |
497 MutableEntry item3(&trans, GET_BY_ID, id3); | |
498 ASSERT_TRUE(item3.good()); | |
499 item3.PutServerIsDel(true); | |
500 // Expect only the first two items to be in the delete journal. | |
501 EntryKernel tmp; | |
502 tmp.put(ID, id1); | |
503 EXPECT_TRUE(delete_journal->delete_journals_.count(&tmp)); | |
504 tmp.put(ID, id2); | |
505 EXPECT_TRUE(delete_journal->delete_journals_.count(&tmp)); | |
506 tmp.put(ID, id3); | |
507 EXPECT_FALSE(delete_journal->delete_journals_.count(&tmp)); | |
508 } | |
509 | |
510 // Save delete journals in database and verify memory clearing. | |
511 ASSERT_TRUE(dir()->SaveChanges()); | |
512 { | |
513 ReadTransaction trans(FROM_HERE, dir().get()); | |
514 EXPECT_EQ(0u, delete_journal->GetDeleteJournalSize(&trans)); | |
515 } | |
516 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); | |
517 } | |
518 | |
519 { | |
520 { | |
521 // Test reading delete journals from database. | |
522 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
523 DeleteJournal* delete_journal = dir()->delete_journal(); | |
524 EntryKernelSet journal_entries; | |
525 delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries); | |
526 ASSERT_EQ(2u, journal_entries.size()); | |
527 EntryKernel tmp; | |
528 tmp.put(META_HANDLE, handle1); | |
529 EXPECT_TRUE(journal_entries.count(&tmp)); | |
530 tmp.put(META_HANDLE, handle2); | |
531 EXPECT_TRUE(journal_entries.count(&tmp)); | |
532 tmp.put(META_HANDLE, handle3); | |
533 EXPECT_FALSE(journal_entries.count(&tmp)); | |
534 | |
535 // Purge item2. | |
536 MetahandleSet to_purge; | |
537 to_purge.insert(handle2); | |
538 delete_journal->PurgeDeleteJournals(&trans, to_purge); | |
539 | |
540 // Verify that item2 is purged from journals in memory and will be | |
541 // purged from database. | |
542 tmp.put(ID, id2); | |
543 EXPECT_FALSE(delete_journal->delete_journals_.count(&tmp)); | |
544 EXPECT_EQ(1u, delete_journal->delete_journals_to_purge_.size()); | |
545 EXPECT_TRUE(delete_journal->delete_journals_to_purge_.count(handle2)); | |
546 } | |
547 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); | |
548 } | |
549 | |
550 { | |
551 { | |
552 // Verify purged entry is gone in database. | |
553 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
554 DeleteJournal* delete_journal = dir()->delete_journal(); | |
555 EntryKernelSet journal_entries; | |
556 delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries); | |
557 ASSERT_EQ(1u, journal_entries.size()); | |
558 EntryKernel tmp; | |
559 tmp.put(ID, id1); | |
560 tmp.put(META_HANDLE, handle1); | |
561 EXPECT_TRUE(journal_entries.count(&tmp)); | |
562 | |
563 // Undelete item1 (IS_UNAPPLIED_UPDATE shouldn't matter in this case). | |
564 MutableEntry item1(&trans, GET_BY_ID, id1); | |
565 ASSERT_TRUE(item1.good()); | |
566 item1.PutIsUnappliedUpdate(false); | |
567 item1.PutServerIsDel(false); | |
568 EXPECT_TRUE(delete_journal->delete_journals_.empty()); | |
569 EXPECT_EQ(1u, delete_journal->delete_journals_to_purge_.size()); | |
570 EXPECT_TRUE(delete_journal->delete_journals_to_purge_.count(handle1)); | |
571 } | |
572 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); | |
573 } | |
574 | |
575 { | |
576 // Verify undeleted entry is gone from database. | |
577 ReadTransaction trans(FROM_HERE, dir().get()); | |
578 DeleteJournal* delete_journal = dir()->delete_journal(); | |
579 ASSERT_EQ(0u, delete_journal->GetDeleteJournalSize(&trans)); | |
580 } | |
581 } | |
582 | |
583 TEST_F(SyncableDirectoryTest, TestPurgeDeletedEntriesOnReload) { | |
584 sync_pb::EntitySpecifics specifics; | |
585 AddDefaultFieldValue(PREFERENCES, &specifics); | |
586 | |
587 const int kClientCount = 2; | |
588 const int kServerCount = 5; | |
589 const int kTestCount = kClientCount + kServerCount; | |
590 int64_t handles[kTestCount]; | |
591 | |
592 // The idea is to recreate various combinations of IDs, IS_DEL, | |
593 // IS_UNSYNCED, and IS_UNAPPLIED_UPDATE flags to test all combinations | |
594 // for DirectoryBackingStore::SafeToPurgeOnLoading. | |
595 // 0: client ID, IS_DEL, IS_UNSYNCED | |
596 // 1: client ID, IS_UNSYNCED | |
597 // 2: server ID, IS_DEL, IS_UNSYNCED, IS_UNAPPLIED_UPDATE | |
598 // 3: server ID, IS_DEL, IS_UNSYNCED | |
599 // 4: server ID, IS_DEL, IS_UNAPPLIED_UPDATE | |
600 // 5: server ID, IS_DEL | |
601 // 6: server ID | |
602 { | |
603 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
604 | |
605 for (int i = 0; i < kTestCount; i++) { | |
606 std::string name = base::StringPrintf("item%d", i); | |
607 MutableEntry item(&trans, CREATE, PREFERENCES, trans.root_id(), name); | |
608 ASSERT_TRUE(item.good()); | |
609 | |
610 handles[i] = item.GetMetahandle(); | |
611 | |
612 if (i < kClientCount) { | |
613 item.PutId(TestIdFactory::FromNumber(i - kClientCount)); | |
614 } else { | |
615 item.PutId(TestIdFactory::FromNumber(i)); | |
616 } | |
617 | |
618 item.PutUniqueClientTag(name); | |
619 item.PutIsUnsynced(true); | |
620 item.PutSpecifics(specifics); | |
621 item.PutServerSpecifics(specifics); | |
622 | |
623 if (i >= kClientCount) { | |
624 item.PutBaseVersion(10); | |
625 item.PutServerVersion(10); | |
626 } | |
627 | |
628 // Set flags | |
629 if (i != 1 && i != 6) | |
630 item.PutIsDel(true); | |
631 | |
632 if (i >= 4) | |
633 item.PutIsUnsynced(false); | |
634 | |
635 if (i == 2 || i == 4) | |
636 item.PutIsUnappliedUpdate(true); | |
637 } | |
638 } | |
639 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); | |
640 | |
641 // Expect items 0 and 5 to be purged according to | |
642 // DirectoryBackingStore::SafeToPurgeOnLoading: | |
643 // - Item 0 is an item with IS_DEL flag and client ID. | |
644 // - Item 5 is an item with IS_DEL flag which has both | |
645 // IS_UNSYNCED and IS_UNAPPLIED_UPDATE unset. | |
646 std::vector<int64_t> expected_purged; | |
647 expected_purged.push_back(0); | |
648 expected_purged.push_back(5); | |
649 | |
650 std::vector<int64_t> actually_purged; | |
651 { | |
652 ReadTransaction trans(FROM_HERE, dir().get()); | |
653 for (int i = 0; i < kTestCount; i++) { | |
654 Entry item(&trans, GET_BY_HANDLE, handles[i]); | |
655 if (!item.good()) { | |
656 actually_purged.push_back(i); | |
657 } | |
658 } | |
659 } | |
660 | |
661 EXPECT_EQ(expected_purged, actually_purged); | |
662 } | |
663 | |
664 TEST_F(SyncableDirectoryTest, TestBasicLookupNonExistantID) { | |
665 ReadTransaction rtrans(FROM_HERE, dir().get()); | |
666 Entry e(&rtrans, GET_BY_ID, TestIdFactory::FromNumber(-99)); | |
667 ASSERT_FALSE(e.good()); | |
668 } | |
669 | |
670 TEST_F(SyncableDirectoryTest, TestBasicLookupValidID) { | |
671 CreateEntry(BOOKMARKS, "rtc"); | |
672 ReadTransaction rtrans(FROM_HERE, dir().get()); | |
673 Entry e(&rtrans, GET_BY_ID, TestIdFactory::FromNumber(-99)); | |
674 ASSERT_TRUE(e.good()); | |
675 } | |
676 | |
677 TEST_F(SyncableDirectoryTest, TestDelete) { | |
678 std::string name = "peanut butter jelly time"; | |
679 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
680 MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), name); | |
681 ASSERT_TRUE(e1.good()); | |
682 e1.PutIsDel(true); | |
683 MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), name); | |
684 ASSERT_TRUE(e2.good()); | |
685 e2.PutIsDel(true); | |
686 MutableEntry e3(&trans, CREATE, BOOKMARKS, trans.root_id(), name); | |
687 ASSERT_TRUE(e3.good()); | |
688 e3.PutIsDel(true); | |
689 | |
690 e1.PutIsDel(false); | |
691 e2.PutIsDel(false); | |
692 e3.PutIsDel(false); | |
693 | |
694 e1.PutIsDel(true); | |
695 e2.PutIsDel(true); | |
696 e3.PutIsDel(true); | |
697 } | |
698 | |
699 TEST_F(SyncableDirectoryTest, TestGetUnsynced) { | |
700 Directory::Metahandles handles; | |
701 int64_t handle1, handle2; | |
702 { | |
703 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
704 | |
705 dir()->GetUnsyncedMetaHandles(&trans, &handles); | |
706 ASSERT_EQ(0u, handles.size()); | |
707 | |
708 MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "abba"); | |
709 ASSERT_TRUE(e1.good()); | |
710 handle1 = e1.GetMetahandle(); | |
711 e1.PutBaseVersion(1); | |
712 e1.PutIsDir(true); | |
713 e1.PutId(TestIdFactory::FromNumber(101)); | |
714 | |
715 MutableEntry e2(&trans, CREATE, BOOKMARKS, e1.GetId(), "bread"); | |
716 ASSERT_TRUE(e2.good()); | |
717 handle2 = e2.GetMetahandle(); | |
718 e2.PutBaseVersion(1); | |
719 e2.PutId(TestIdFactory::FromNumber(102)); | |
720 } | |
721 dir()->SaveChanges(); | |
722 { | |
723 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
724 | |
725 dir()->GetUnsyncedMetaHandles(&trans, &handles); | |
726 ASSERT_EQ(0u, handles.size()); | |
727 | |
728 MutableEntry e3(&trans, GET_BY_HANDLE, handle1); | |
729 ASSERT_TRUE(e3.good()); | |
730 e3.PutIsUnsynced(true); | |
731 } | |
732 dir()->SaveChanges(); | |
733 { | |
734 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
735 dir()->GetUnsyncedMetaHandles(&trans, &handles); | |
736 ASSERT_EQ(1u, handles.size()); | |
737 ASSERT_TRUE(handle1 == handles[0]); | |
738 | |
739 MutableEntry e4(&trans, GET_BY_HANDLE, handle2); | |
740 ASSERT_TRUE(e4.good()); | |
741 e4.PutIsUnsynced(true); | |
742 } | |
743 dir()->SaveChanges(); | |
744 { | |
745 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
746 dir()->GetUnsyncedMetaHandles(&trans, &handles); | |
747 ASSERT_EQ(2u, handles.size()); | |
748 if (handle1 == handles[0]) { | |
749 ASSERT_TRUE(handle2 == handles[1]); | |
750 } else { | |
751 ASSERT_TRUE(handle2 == handles[0]); | |
752 ASSERT_TRUE(handle1 == handles[1]); | |
753 } | |
754 | |
755 MutableEntry e5(&trans, GET_BY_HANDLE, handle1); | |
756 ASSERT_TRUE(e5.good()); | |
757 ASSERT_TRUE(e5.GetIsUnsynced()); | |
758 ASSERT_TRUE(e5.PutIsUnsynced(false)); | |
759 ASSERT_FALSE(e5.GetIsUnsynced()); | |
760 } | |
761 dir()->SaveChanges(); | |
762 { | |
763 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
764 dir()->GetUnsyncedMetaHandles(&trans, &handles); | |
765 ASSERT_EQ(1u, handles.size()); | |
766 ASSERT_TRUE(handle2 == handles[0]); | |
767 } | |
768 } | |
769 | |
770 TEST_F(SyncableDirectoryTest, TestGetUnappliedUpdates) { | |
771 std::vector<int64_t> handles; | |
772 int64_t handle1, handle2; | |
773 const FullModelTypeSet all_types = FullModelTypeSet::All(); | |
774 { | |
775 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
776 | |
777 dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); | |
778 ASSERT_EQ(0u, handles.size()); | |
779 | |
780 MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "abba"); | |
781 ASSERT_TRUE(e1.good()); | |
782 handle1 = e1.GetMetahandle(); | |
783 e1.PutIsUnappliedUpdate(false); | |
784 e1.PutBaseVersion(1); | |
785 e1.PutId(TestIdFactory::FromNumber(101)); | |
786 e1.PutIsDir(true); | |
787 | |
788 MutableEntry e2(&trans, CREATE, BOOKMARKS, e1.GetId(), "bread"); | |
789 ASSERT_TRUE(e2.good()); | |
790 handle2 = e2.GetMetahandle(); | |
791 e2.PutIsUnappliedUpdate(false); | |
792 e2.PutBaseVersion(1); | |
793 e2.PutId(TestIdFactory::FromNumber(102)); | |
794 } | |
795 dir()->SaveChanges(); | |
796 { | |
797 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
798 | |
799 dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); | |
800 ASSERT_EQ(0u, handles.size()); | |
801 | |
802 MutableEntry e3(&trans, GET_BY_HANDLE, handle1); | |
803 ASSERT_TRUE(e3.good()); | |
804 e3.PutIsUnappliedUpdate(true); | |
805 } | |
806 dir()->SaveChanges(); | |
807 { | |
808 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
809 dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); | |
810 ASSERT_EQ(1u, handles.size()); | |
811 ASSERT_TRUE(handle1 == handles[0]); | |
812 | |
813 MutableEntry e4(&trans, GET_BY_HANDLE, handle2); | |
814 ASSERT_TRUE(e4.good()); | |
815 e4.PutIsUnappliedUpdate(true); | |
816 } | |
817 dir()->SaveChanges(); | |
818 { | |
819 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
820 dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); | |
821 ASSERT_EQ(2u, handles.size()); | |
822 if (handle1 == handles[0]) { | |
823 ASSERT_TRUE(handle2 == handles[1]); | |
824 } else { | |
825 ASSERT_TRUE(handle2 == handles[0]); | |
826 ASSERT_TRUE(handle1 == handles[1]); | |
827 } | |
828 | |
829 MutableEntry e5(&trans, GET_BY_HANDLE, handle1); | |
830 ASSERT_TRUE(e5.good()); | |
831 e5.PutIsUnappliedUpdate(false); | |
832 } | |
833 dir()->SaveChanges(); | |
834 { | |
835 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
836 dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); | |
837 ASSERT_EQ(1u, handles.size()); | |
838 ASSERT_TRUE(handle2 == handles[0]); | |
839 } | |
840 } | |
841 | |
842 TEST_F(SyncableDirectoryTest, DeleteBug_531383) { | |
843 // Try to evoke a check failure... | |
844 TestIdFactory id_factory; | |
845 int64_t grandchild_handle; | |
846 { | |
847 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); | |
848 MutableEntry parent(&wtrans, CREATE, BOOKMARKS, id_factory.root(), "Bob"); | |
849 ASSERT_TRUE(parent.good()); | |
850 parent.PutIsDir(true); | |
851 parent.PutId(id_factory.NewServerId()); | |
852 parent.PutBaseVersion(1); | |
853 MutableEntry child(&wtrans, CREATE, BOOKMARKS, parent.GetId(), "Bob"); | |
854 ASSERT_TRUE(child.good()); | |
855 child.PutIsDir(true); | |
856 child.PutId(id_factory.NewServerId()); | |
857 child.PutBaseVersion(1); | |
858 MutableEntry grandchild(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob"); | |
859 ASSERT_TRUE(grandchild.good()); | |
860 grandchild.PutId(id_factory.NewServerId()); | |
861 grandchild.PutBaseVersion(1); | |
862 grandchild.PutIsDel(true); | |
863 MutableEntry twin(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob"); | |
864 ASSERT_TRUE(twin.good()); | |
865 twin.PutIsDel(true); | |
866 grandchild.PutIsDel(false); | |
867 | |
868 grandchild_handle = grandchild.GetMetahandle(); | |
869 } | |
870 dir()->SaveChanges(); | |
871 { | |
872 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); | |
873 MutableEntry grandchild(&wtrans, GET_BY_HANDLE, grandchild_handle); | |
874 grandchild.PutIsDel(true); // Used to CHECK fail here. | |
875 } | |
876 } | |
877 | |
878 TEST_F(SyncableDirectoryTest, TestIsLegalNewParent) { | |
879 TestIdFactory id_factory; | |
880 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); | |
881 Entry root(&wtrans, GET_BY_ID, id_factory.root()); | |
882 ASSERT_TRUE(root.good()); | |
883 MutableEntry parent(&wtrans, CREATE, BOOKMARKS, root.GetId(), "Bob"); | |
884 ASSERT_TRUE(parent.good()); | |
885 parent.PutIsDir(true); | |
886 parent.PutId(id_factory.NewServerId()); | |
887 parent.PutBaseVersion(1); | |
888 MutableEntry child(&wtrans, CREATE, BOOKMARKS, parent.GetId(), "Bob"); | |
889 ASSERT_TRUE(child.good()); | |
890 child.PutIsDir(true); | |
891 child.PutId(id_factory.NewServerId()); | |
892 child.PutBaseVersion(1); | |
893 MutableEntry grandchild(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob"); | |
894 ASSERT_TRUE(grandchild.good()); | |
895 grandchild.PutId(id_factory.NewServerId()); | |
896 grandchild.PutBaseVersion(1); | |
897 | |
898 MutableEntry parent2(&wtrans, CREATE, BOOKMARKS, root.GetId(), "Pete"); | |
899 ASSERT_TRUE(parent2.good()); | |
900 parent2.PutIsDir(true); | |
901 parent2.PutId(id_factory.NewServerId()); | |
902 parent2.PutBaseVersion(1); | |
903 MutableEntry child2(&wtrans, CREATE, BOOKMARKS, parent2.GetId(), "Pete"); | |
904 ASSERT_TRUE(child2.good()); | |
905 child2.PutIsDir(true); | |
906 child2.PutId(id_factory.NewServerId()); | |
907 child2.PutBaseVersion(1); | |
908 MutableEntry grandchild2(&wtrans, CREATE, BOOKMARKS, child2.GetId(), "Pete"); | |
909 ASSERT_TRUE(grandchild2.good()); | |
910 grandchild2.PutId(id_factory.NewServerId()); | |
911 grandchild2.PutBaseVersion(1); | |
912 // resulting tree | |
913 // root | |
914 // / | | |
915 // parent parent2 | |
916 // | | | |
917 // child child2 | |
918 // | | | |
919 // grandchild grandchild2 | |
920 ASSERT_TRUE(IsLegalNewParent(child, root)); | |
921 ASSERT_TRUE(IsLegalNewParent(child, parent)); | |
922 ASSERT_FALSE(IsLegalNewParent(child, child)); | |
923 ASSERT_FALSE(IsLegalNewParent(child, grandchild)); | |
924 ASSERT_TRUE(IsLegalNewParent(child, parent2)); | |
925 ASSERT_TRUE(IsLegalNewParent(child, grandchild2)); | |
926 ASSERT_FALSE(IsLegalNewParent(parent, grandchild)); | |
927 ASSERT_FALSE(IsLegalNewParent(root, grandchild)); | |
928 ASSERT_FALSE(IsLegalNewParent(parent, grandchild)); | |
929 } | |
930 | |
931 TEST_F(SyncableDirectoryTest, TestEntryIsInFolder) { | |
932 // Create a subdir and an entry. | |
933 int64_t entry_handle; | |
934 syncable::Id folder_id; | |
935 syncable::Id entry_id; | |
936 std::string entry_name = "entry"; | |
937 | |
938 { | |
939 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
940 MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "folder"); | |
941 ASSERT_TRUE(folder.good()); | |
942 folder.PutIsDir(true); | |
943 EXPECT_TRUE(folder.PutIsUnsynced(true)); | |
944 folder_id = folder.GetId(); | |
945 | |
946 MutableEntry entry(&trans, CREATE, BOOKMARKS, folder.GetId(), entry_name); | |
947 ASSERT_TRUE(entry.good()); | |
948 entry_handle = entry.GetMetahandle(); | |
949 entry.PutIsUnsynced(true); | |
950 entry_id = entry.GetId(); | |
951 } | |
952 | |
953 // Make sure we can find the entry in the folder. | |
954 { | |
955 ReadTransaction trans(FROM_HERE, dir().get()); | |
956 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), entry_name)); | |
957 EXPECT_EQ(1, CountEntriesWithName(&trans, folder_id, entry_name)); | |
958 | |
959 Entry entry(&trans, GET_BY_ID, entry_id); | |
960 ASSERT_TRUE(entry.good()); | |
961 EXPECT_EQ(entry_handle, entry.GetMetahandle()); | |
962 EXPECT_TRUE(entry.GetNonUniqueName() == entry_name); | |
963 EXPECT_TRUE(entry.GetParentId() == folder_id); | |
964 } | |
965 } | |
966 | |
967 TEST_F(SyncableDirectoryTest, TestParentIdIndexUpdate) { | |
968 std::string child_name = "child"; | |
969 | |
970 WriteTransaction wt(FROM_HERE, UNITTEST, dir().get()); | |
971 MutableEntry parent_folder(&wt, CREATE, BOOKMARKS, wt.root_id(), "folder1"); | |
972 parent_folder.PutIsUnsynced(true); | |
973 parent_folder.PutIsDir(true); | |
974 | |
975 MutableEntry parent_folder2(&wt, CREATE, BOOKMARKS, wt.root_id(), "folder2"); | |
976 parent_folder2.PutIsUnsynced(true); | |
977 parent_folder2.PutIsDir(true); | |
978 | |
979 MutableEntry child(&wt, CREATE, BOOKMARKS, parent_folder.GetId(), child_name); | |
980 child.PutIsDir(true); | |
981 child.PutIsUnsynced(true); | |
982 | |
983 ASSERT_TRUE(child.good()); | |
984 | |
985 EXPECT_EQ(0, CountEntriesWithName(&wt, wt.root_id(), child_name)); | |
986 EXPECT_EQ(parent_folder.GetId(), child.GetParentId()); | |
987 EXPECT_EQ(1, CountEntriesWithName(&wt, parent_folder.GetId(), child_name)); | |
988 EXPECT_EQ(0, CountEntriesWithName(&wt, parent_folder2.GetId(), child_name)); | |
989 child.PutParentId(parent_folder2.GetId()); | |
990 EXPECT_EQ(parent_folder2.GetId(), child.GetParentId()); | |
991 EXPECT_EQ(0, CountEntriesWithName(&wt, parent_folder.GetId(), child_name)); | |
992 EXPECT_EQ(1, CountEntriesWithName(&wt, parent_folder2.GetId(), child_name)); | |
993 } | |
994 | |
995 TEST_F(SyncableDirectoryTest, TestNoReindexDeletedItems) { | |
996 std::string folder_name = "folder"; | |
997 std::string new_name = "new_name"; | |
998 | |
999 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1000 MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), folder_name); | |
1001 ASSERT_TRUE(folder.good()); | |
1002 folder.PutIsDir(true); | |
1003 folder.PutIsDel(true); | |
1004 | |
1005 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), folder_name)); | |
1006 | |
1007 MutableEntry deleted(&trans, GET_BY_ID, folder.GetId()); | |
1008 ASSERT_TRUE(deleted.good()); | |
1009 deleted.PutParentId(trans.root_id()); | |
1010 deleted.PutNonUniqueName(new_name); | |
1011 | |
1012 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), folder_name)); | |
1013 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), new_name)); | |
1014 } | |
1015 | |
1016 TEST_F(SyncableDirectoryTest, TestCaseChangeRename) { | |
1017 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1018 MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "CaseChange"); | |
1019 ASSERT_TRUE(folder.good()); | |
1020 folder.PutParentId(trans.root_id()); | |
1021 folder.PutNonUniqueName("CASECHANGE"); | |
1022 folder.PutIsDel(true); | |
1023 } | |
1024 | |
1025 // Create items of each model type, and check that GetModelType and | |
1026 // GetServerModelType return the right value. | |
1027 TEST_F(SyncableDirectoryTest, GetModelType) { | |
1028 TestIdFactory id_factory; | |
1029 ModelTypeSet protocol_types = ProtocolTypes(); | |
1030 for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good(); | |
1031 iter.Inc()) { | |
1032 ModelType datatype = iter.Get(); | |
1033 SCOPED_TRACE(testing::Message("Testing model type ") << datatype); | |
1034 switch (datatype) { | |
1035 case UNSPECIFIED: | |
1036 case TOP_LEVEL_FOLDER: | |
1037 continue; // Datatype isn't a function of Specifics. | |
1038 default: | |
1039 break; | |
1040 } | |
1041 sync_pb::EntitySpecifics specifics; | |
1042 AddDefaultFieldValue(datatype, &specifics); | |
1043 | |
1044 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1045 | |
1046 MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "Folder"); | |
1047 ASSERT_TRUE(folder.good()); | |
1048 folder.PutId(id_factory.NewServerId()); | |
1049 folder.PutSpecifics(specifics); | |
1050 folder.PutBaseVersion(1); | |
1051 folder.PutIsDir(true); | |
1052 folder.PutIsDel(false); | |
1053 ASSERT_EQ(datatype, folder.GetModelType()); | |
1054 | |
1055 MutableEntry item(&trans, CREATE, BOOKMARKS, trans.root_id(), "Item"); | |
1056 ASSERT_TRUE(item.good()); | |
1057 item.PutId(id_factory.NewServerId()); | |
1058 item.PutSpecifics(specifics); | |
1059 item.PutBaseVersion(1); | |
1060 item.PutIsDir(false); | |
1061 item.PutIsDel(false); | |
1062 ASSERT_EQ(datatype, item.GetModelType()); | |
1063 | |
1064 // It's critical that deletion records retain their datatype, so that | |
1065 // they can be dispatched to the appropriate change processor. | |
1066 MutableEntry deleted_item( | |
1067 &trans, CREATE, BOOKMARKS, trans.root_id(), "Deleted Item"); | |
1068 ASSERT_TRUE(item.good()); | |
1069 deleted_item.PutId(id_factory.NewServerId()); | |
1070 deleted_item.PutSpecifics(specifics); | |
1071 deleted_item.PutBaseVersion(1); | |
1072 deleted_item.PutIsDir(false); | |
1073 deleted_item.PutIsDel(true); | |
1074 ASSERT_EQ(datatype, deleted_item.GetModelType()); | |
1075 | |
1076 MutableEntry server_folder( | |
1077 &trans, CREATE_NEW_UPDATE_ITEM, id_factory.NewServerId()); | |
1078 ASSERT_TRUE(server_folder.good()); | |
1079 server_folder.PutServerSpecifics(specifics); | |
1080 server_folder.PutBaseVersion(1); | |
1081 server_folder.PutServerIsDir(true); | |
1082 server_folder.PutServerIsDel(false); | |
1083 ASSERT_EQ(datatype, server_folder.GetServerModelType()); | |
1084 | |
1085 MutableEntry server_item( | |
1086 &trans, CREATE_NEW_UPDATE_ITEM, id_factory.NewServerId()); | |
1087 ASSERT_TRUE(server_item.good()); | |
1088 server_item.PutServerSpecifics(specifics); | |
1089 server_item.PutBaseVersion(1); | |
1090 server_item.PutServerIsDir(false); | |
1091 server_item.PutServerIsDel(false); | |
1092 ASSERT_EQ(datatype, server_item.GetServerModelType()); | |
1093 | |
1094 sync_pb::SyncEntity folder_entity; | |
1095 folder_entity.set_id_string(SyncableIdToProto(id_factory.NewServerId())); | |
1096 folder_entity.set_deleted(false); | |
1097 folder_entity.set_folder(true); | |
1098 folder_entity.mutable_specifics()->CopyFrom(specifics); | |
1099 ASSERT_EQ(datatype, GetModelType(folder_entity)); | |
1100 | |
1101 sync_pb::SyncEntity item_entity; | |
1102 item_entity.set_id_string(SyncableIdToProto(id_factory.NewServerId())); | |
1103 item_entity.set_deleted(false); | |
1104 item_entity.set_folder(false); | |
1105 item_entity.mutable_specifics()->CopyFrom(specifics); | |
1106 ASSERT_EQ(datatype, GetModelType(item_entity)); | |
1107 } | |
1108 } | |
1109 | |
1110 // A test that roughly mimics the directory interaction that occurs when a | |
1111 // bookmark folder and entry are created then synced for the first time. It is | |
1112 // a more common variant of the 'DeletedAndUnsyncedChild' scenario tested below. | |
1113 TEST_F(SyncableDirectoryTest, ChangeEntryIDAndUpdateChildren_ParentAndChild) { | |
1114 TestIdFactory id_factory; | |
1115 Id orig_parent_id; | |
1116 Id orig_child_id; | |
1117 | |
1118 { | |
1119 // Create two client-side items, a parent and child. | |
1120 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1121 | |
1122 MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent"); | |
1123 parent.PutIsDir(true); | |
1124 parent.PutIsUnsynced(true); | |
1125 | |
1126 MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child"); | |
1127 child.PutIsUnsynced(true); | |
1128 | |
1129 orig_parent_id = parent.GetId(); | |
1130 orig_child_id = child.GetId(); | |
1131 } | |
1132 | |
1133 { | |
1134 // Simulate what happens after committing two items. Their IDs will be | |
1135 // replaced with server IDs. The child is renamed first, then the parent. | |
1136 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1137 | |
1138 MutableEntry parent(&trans, GET_BY_ID, orig_parent_id); | |
1139 MutableEntry child(&trans, GET_BY_ID, orig_child_id); | |
1140 | |
1141 ChangeEntryIDAndUpdateChildren(&trans, &child, id_factory.NewServerId()); | |
1142 child.PutIsUnsynced(false); | |
1143 child.PutBaseVersion(1); | |
1144 child.PutServerVersion(1); | |
1145 | |
1146 ChangeEntryIDAndUpdateChildren(&trans, &parent, id_factory.NewServerId()); | |
1147 parent.PutIsUnsynced(false); | |
1148 parent.PutBaseVersion(1); | |
1149 parent.PutServerVersion(1); | |
1150 } | |
1151 | |
1152 // Final check for validity. | |
1153 EXPECT_EQ(OPENED, SimulateSaveAndReloadDir()); | |
1154 } | |
1155 | |
1156 // A test that roughly mimics the directory interaction that occurs when a | |
1157 // type root folder is created locally and then re-created (updated) from the | |
1158 // server. | |
1159 TEST_F(SyncableDirectoryTest, ChangeEntryIDAndUpdateChildren_ImplicitParent) { | |
1160 TestIdFactory id_factory; | |
1161 Id orig_parent_id; | |
1162 Id child_id; | |
1163 | |
1164 { | |
1165 // Create two client-side items, a parent and child. | |
1166 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1167 | |
1168 MutableEntry parent(&trans, CREATE, PREFERENCES, id_factory.root(), | |
1169 "parent"); | |
1170 parent.PutIsDir(true); | |
1171 parent.PutIsUnsynced(true); | |
1172 | |
1173 // The child has unset parent ID. The parent is inferred from the type. | |
1174 MutableEntry child(&trans, CREATE, PREFERENCES, "child"); | |
1175 child.PutIsUnsynced(true); | |
1176 | |
1177 orig_parent_id = parent.GetId(); | |
1178 child_id = child.GetId(); | |
1179 } | |
1180 | |
1181 { | |
1182 // Simulate what happens after committing two items. Their IDs will be | |
1183 // replaced with server IDs. The child is renamed first, then the parent. | |
1184 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1185 | |
1186 MutableEntry parent(&trans, GET_BY_ID, orig_parent_id); | |
1187 | |
1188 ChangeEntryIDAndUpdateChildren(&trans, &parent, id_factory.NewServerId()); | |
1189 parent.PutIsUnsynced(false); | |
1190 parent.PutBaseVersion(1); | |
1191 parent.PutServerVersion(1); | |
1192 } | |
1193 | |
1194 // Final check for validity. | |
1195 EXPECT_EQ(OPENED, SimulateSaveAndReloadDir()); | |
1196 | |
1197 // Verify that child's PARENT_ID hasn't been updated. | |
1198 { | |
1199 ReadTransaction trans(FROM_HERE, dir().get()); | |
1200 Entry child(&trans, GET_BY_ID, child_id); | |
1201 EXPECT_TRUE(child.good()); | |
1202 EXPECT_TRUE(child.GetParentId().IsNull()); | |
1203 } | |
1204 } | |
1205 | |
1206 // A test based on the scenario where we create a bookmark folder and entry | |
1207 // locally, but with a twist. In this case, the bookmark is deleted before we | |
1208 // are able to sync either it or its parent folder. This scenario used to cause | |
1209 // directory corruption, see crbug.com/125381. | |
1210 TEST_F(SyncableDirectoryTest, | |
1211 ChangeEntryIDAndUpdateChildren_DeletedAndUnsyncedChild) { | |
1212 TestIdFactory id_factory; | |
1213 Id orig_parent_id; | |
1214 Id orig_child_id; | |
1215 | |
1216 { | |
1217 // Create two client-side items, a parent and child. | |
1218 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1219 | |
1220 MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent"); | |
1221 parent.PutIsDir(true); | |
1222 parent.PutIsUnsynced(true); | |
1223 | |
1224 MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child"); | |
1225 child.PutIsUnsynced(true); | |
1226 | |
1227 orig_parent_id = parent.GetId(); | |
1228 orig_child_id = child.GetId(); | |
1229 } | |
1230 | |
1231 { | |
1232 // Delete the child. | |
1233 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1234 | |
1235 MutableEntry child(&trans, GET_BY_ID, orig_child_id); | |
1236 child.PutIsDel(true); | |
1237 } | |
1238 | |
1239 { | |
1240 // Simulate what happens after committing the parent. Its ID will be | |
1241 // replaced with server a ID. | |
1242 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1243 | |
1244 MutableEntry parent(&trans, GET_BY_ID, orig_parent_id); | |
1245 | |
1246 ChangeEntryIDAndUpdateChildren(&trans, &parent, id_factory.NewServerId()); | |
1247 parent.PutIsUnsynced(false); | |
1248 parent.PutBaseVersion(1); | |
1249 parent.PutServerVersion(1); | |
1250 } | |
1251 | |
1252 // Final check for validity. | |
1253 EXPECT_EQ(OPENED, SimulateSaveAndReloadDir()); | |
1254 } | |
1255 | |
1256 // Ask the directory to generate a unique ID. Close and re-open the database | |
1257 // without saving, then ask for another unique ID. Verify IDs are not reused. | |
1258 // This scenario simulates a crash within the first few seconds of operation. | |
1259 TEST_F(SyncableDirectoryTest, LocalIdReuseTest) { | |
1260 Id pre_crash_id = dir()->NextId(); | |
1261 SimulateCrashAndReloadDir(); | |
1262 Id post_crash_id = dir()->NextId(); | |
1263 EXPECT_NE(pre_crash_id, post_crash_id); | |
1264 } | |
1265 | |
1266 // Ask the directory to generate a unique ID. Save the directory. Close and | |
1267 // re-open the database without saving, then ask for another unique ID. Verify | |
1268 // IDs are not reused. This scenario simulates a steady-state crash. | |
1269 TEST_F(SyncableDirectoryTest, LocalIdReuseTestWithSave) { | |
1270 Id pre_crash_id = dir()->NextId(); | |
1271 dir()->SaveChanges(); | |
1272 SimulateCrashAndReloadDir(); | |
1273 Id post_crash_id = dir()->NextId(); | |
1274 EXPECT_NE(pre_crash_id, post_crash_id); | |
1275 } | |
1276 | |
1277 // Ensure that the unsynced, is_del and server unkown entries that may have been | |
1278 // left in the database by old clients will be deleted when we open the old | |
1279 // database. | |
1280 TEST_F(SyncableDirectoryTest, OldClientLeftUnsyncedDeletedLocalItem) { | |
1281 // We must create an entry with the offending properties. This is done with | |
1282 // some abuse of the MutableEntry's API; it doesn't expect us to modify an | |
1283 // item after it is deleted. If this hack becomes impractical we will need to | |
1284 // find a new way to simulate this scenario. | |
1285 | |
1286 TestIdFactory id_factory; | |
1287 | |
1288 // Happy-path: These valid entries should not get deleted. | |
1289 Id server_knows_id = id_factory.NewServerId(); | |
1290 Id not_is_del_id = id_factory.NewLocalId(); | |
1291 | |
1292 // The ID of the entry which will be unsynced, is_del and !ServerKnows(). | |
1293 Id zombie_id = id_factory.NewLocalId(); | |
1294 | |
1295 // We're about to do some bad things. Tell the directory verification | |
1296 // routines to look the other way. | |
1297 dir()->SetInvariantCheckLevel(OFF); | |
1298 | |
1299 { | |
1300 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1301 | |
1302 // Create an uncommitted tombstone entry. | |
1303 MutableEntry server_knows( | |
1304 &trans, CREATE, BOOKMARKS, id_factory.root(), "server_knows"); | |
1305 server_knows.PutId(server_knows_id); | |
1306 server_knows.PutIsUnsynced(true); | |
1307 server_knows.PutIsDel(true); | |
1308 server_knows.PutBaseVersion(5); | |
1309 server_knows.PutServerVersion(4); | |
1310 | |
1311 // Create a valid update entry. | |
1312 MutableEntry not_is_del( | |
1313 &trans, CREATE, BOOKMARKS, id_factory.root(), "not_is_del"); | |
1314 not_is_del.PutId(not_is_del_id); | |
1315 not_is_del.PutIsDel(false); | |
1316 not_is_del.PutIsUnsynced(true); | |
1317 | |
1318 // Create a tombstone which should never be sent to the server because the | |
1319 // server never knew about the item's existence. | |
1320 // | |
1321 // New clients should never put entries into this state. We work around | |
1322 // this by setting IS_DEL before setting IS_UNSYNCED, something which the | |
1323 // client should never do in practice. | |
1324 MutableEntry zombie(&trans, CREATE, BOOKMARKS, id_factory.root(), "zombie"); | |
1325 zombie.PutId(zombie_id); | |
1326 zombie.PutIsDel(true); | |
1327 zombie.PutIsUnsynced(true); | |
1328 } | |
1329 | |
1330 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); | |
1331 | |
1332 { | |
1333 ReadTransaction trans(FROM_HERE, dir().get()); | |
1334 | |
1335 // The directory loading routines should have cleaned things up, making it | |
1336 // safe to check invariants once again. | |
1337 dir()->FullyCheckTreeInvariants(&trans); | |
1338 | |
1339 Entry server_knows(&trans, GET_BY_ID, server_knows_id); | |
1340 EXPECT_TRUE(server_knows.good()); | |
1341 | |
1342 Entry not_is_del(&trans, GET_BY_ID, not_is_del_id); | |
1343 EXPECT_TRUE(not_is_del.good()); | |
1344 | |
1345 Entry zombie(&trans, GET_BY_ID, zombie_id); | |
1346 EXPECT_FALSE(zombie.good()); | |
1347 } | |
1348 } | |
1349 | |
1350 TEST_F(SyncableDirectoryTest, PositionWithNullSurvivesSaveAndReload) { | |
1351 TestIdFactory id_factory; | |
1352 Id null_child_id; | |
1353 const char null_cstr[] = "\0null\0test"; | |
1354 std::string null_str(null_cstr, arraysize(null_cstr) - 1); | |
1355 // Pad up to the minimum length with 0x7f characters, then add a string that | |
1356 // contains a few NULLs to the end. This is slightly wrong, since the suffix | |
1357 // part of a UniquePosition shouldn't contain NULLs, but it's good enough for | |
1358 // this test. | |
1359 std::string suffix = | |
1360 std::string(UniquePosition::kSuffixLength - null_str.length(), '\x7f') + | |
1361 null_str; | |
1362 UniquePosition null_pos = UniquePosition::FromInt64(10, suffix); | |
1363 | |
1364 { | |
1365 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1366 | |
1367 MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent"); | |
1368 parent.PutIsDir(true); | |
1369 parent.PutIsUnsynced(true); | |
1370 | |
1371 MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child"); | |
1372 child.PutIsUnsynced(true); | |
1373 child.PutUniquePosition(null_pos); | |
1374 child.PutServerUniquePosition(null_pos); | |
1375 | |
1376 null_child_id = child.GetId(); | |
1377 } | |
1378 | |
1379 EXPECT_EQ(OPENED, SimulateSaveAndReloadDir()); | |
1380 | |
1381 { | |
1382 ReadTransaction trans(FROM_HERE, dir().get()); | |
1383 | |
1384 Entry null_ordinal_child(&trans, GET_BY_ID, null_child_id); | |
1385 EXPECT_TRUE(null_pos.Equals(null_ordinal_child.GetUniquePosition())); | |
1386 EXPECT_TRUE(null_pos.Equals(null_ordinal_child.GetServerUniquePosition())); | |
1387 } | |
1388 } | |
1389 | |
1390 // Any item with BOOKMARKS in their local specifics should have a valid local | |
1391 // unique position. If there is an item in the loaded DB that does not match | |
1392 // this criteria, we consider the whole DB to be corrupt. | |
1393 TEST_F(SyncableDirectoryTest, BadPositionCountsAsCorruption) { | |
1394 TestIdFactory id_factory; | |
1395 | |
1396 { | |
1397 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1398 | |
1399 MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent"); | |
1400 parent.PutIsDir(true); | |
1401 parent.PutIsUnsynced(true); | |
1402 | |
1403 // The code is littered with DCHECKs that try to stop us from doing what | |
1404 // we're about to do. Our work-around is to create a bookmark based on | |
1405 // a server update, then update its local specifics without updating its | |
1406 // local unique position. | |
1407 | |
1408 MutableEntry child( | |
1409 &trans, CREATE_NEW_UPDATE_ITEM, id_factory.MakeServer("child")); | |
1410 sync_pb::EntitySpecifics specifics; | |
1411 AddDefaultFieldValue(BOOKMARKS, &specifics); | |
1412 child.PutIsUnappliedUpdate(true); | |
1413 child.PutSpecifics(specifics); | |
1414 | |
1415 EXPECT_TRUE(child.ShouldMaintainPosition()); | |
1416 EXPECT_TRUE(!child.GetUniquePosition().IsValid()); | |
1417 } | |
1418 | |
1419 EXPECT_EQ(FAILED_DATABASE_CORRUPT, SimulateSaveAndReloadDir()); | |
1420 } | |
1421 | |
1422 TEST_F(SyncableDirectoryTest, General) { | |
1423 int64_t written_metahandle; | |
1424 const Id id = TestIdFactory::FromNumber(99); | |
1425 std::string name = "Jeff"; | |
1426 // Test simple read operations on an empty DB. | |
1427 { | |
1428 ReadTransaction rtrans(FROM_HERE, dir().get()); | |
1429 Entry e(&rtrans, GET_BY_ID, id); | |
1430 ASSERT_FALSE(e.good()); // Hasn't been written yet. | |
1431 | |
1432 Directory::Metahandles child_handles; | |
1433 dir()->GetChildHandlesById(&rtrans, rtrans.root_id(), &child_handles); | |
1434 EXPECT_TRUE(child_handles.empty()); | |
1435 } | |
1436 | |
1437 // Test creating a new meta entry. | |
1438 { | |
1439 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); | |
1440 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), name); | |
1441 ASSERT_TRUE(me.good()); | |
1442 me.PutId(id); | |
1443 me.PutBaseVersion(1); | |
1444 written_metahandle = me.GetMetahandle(); | |
1445 } | |
1446 | |
1447 // Test GetChildHandles* after something is now in the DB. | |
1448 // Also check that GET_BY_ID works. | |
1449 { | |
1450 ReadTransaction rtrans(FROM_HERE, dir().get()); | |
1451 Entry e(&rtrans, GET_BY_ID, id); | |
1452 ASSERT_TRUE(e.good()); | |
1453 | |
1454 Directory::Metahandles child_handles; | |
1455 dir()->GetChildHandlesById(&rtrans, rtrans.root_id(), &child_handles); | |
1456 EXPECT_EQ(1u, child_handles.size()); | |
1457 | |
1458 for (Directory::Metahandles::iterator i = child_handles.begin(); | |
1459 i != child_handles.end(); ++i) { | |
1460 EXPECT_EQ(*i, written_metahandle); | |
1461 } | |
1462 } | |
1463 | |
1464 // Test writing data to an entity. Also check that GET_BY_HANDLE works. | |
1465 static const char s[] = "Hello World."; | |
1466 { | |
1467 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1468 MutableEntry e(&trans, GET_BY_HANDLE, written_metahandle); | |
1469 ASSERT_TRUE(e.good()); | |
1470 PutDataAsBookmarkFavicon(&trans, &e, s, sizeof(s)); | |
1471 } | |
1472 | |
1473 // Test reading back the contents that we just wrote. | |
1474 { | |
1475 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1476 MutableEntry e(&trans, GET_BY_HANDLE, written_metahandle); | |
1477 ASSERT_TRUE(e.good()); | |
1478 ExpectDataFromBookmarkFaviconEquals(&trans, &e, s, sizeof(s)); | |
1479 } | |
1480 | |
1481 // Verify it exists in the folder. | |
1482 { | |
1483 ReadTransaction rtrans(FROM_HERE, dir().get()); | |
1484 EXPECT_EQ(1, CountEntriesWithName(&rtrans, rtrans.root_id(), name)); | |
1485 } | |
1486 | |
1487 // Now delete it. | |
1488 { | |
1489 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1490 MutableEntry e(&trans, GET_BY_HANDLE, written_metahandle); | |
1491 e.PutIsDel(true); | |
1492 | |
1493 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), name)); | |
1494 } | |
1495 | |
1496 dir()->SaveChanges(); | |
1497 } | |
1498 | |
1499 TEST_F(SyncableDirectoryTest, ChildrenOps) { | |
1500 int64_t written_metahandle; | |
1501 const Id id = TestIdFactory::FromNumber(99); | |
1502 std::string name = "Jeff"; | |
1503 { | |
1504 ReadTransaction rtrans(FROM_HERE, dir().get()); | |
1505 Entry e(&rtrans, GET_BY_ID, id); | |
1506 ASSERT_FALSE(e.good()); // Hasn't been written yet. | |
1507 | |
1508 Entry root(&rtrans, GET_BY_ID, rtrans.root_id()); | |
1509 ASSERT_TRUE(root.good()); | |
1510 EXPECT_FALSE(dir()->HasChildren(&rtrans, rtrans.root_id())); | |
1511 EXPECT_TRUE(root.GetFirstChildId().IsNull()); | |
1512 } | |
1513 | |
1514 { | |
1515 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); | |
1516 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), name); | |
1517 ASSERT_TRUE(me.good()); | |
1518 me.PutId(id); | |
1519 me.PutBaseVersion(1); | |
1520 written_metahandle = me.GetMetahandle(); | |
1521 } | |
1522 | |
1523 // Test children ops after something is now in the DB. | |
1524 { | |
1525 ReadTransaction rtrans(FROM_HERE, dir().get()); | |
1526 Entry e(&rtrans, GET_BY_ID, id); | |
1527 ASSERT_TRUE(e.good()); | |
1528 | |
1529 Entry child(&rtrans, GET_BY_HANDLE, written_metahandle); | |
1530 ASSERT_TRUE(child.good()); | |
1531 | |
1532 Entry root(&rtrans, GET_BY_ID, rtrans.root_id()); | |
1533 ASSERT_TRUE(root.good()); | |
1534 EXPECT_TRUE(dir()->HasChildren(&rtrans, rtrans.root_id())); | |
1535 EXPECT_EQ(e.GetId(), root.GetFirstChildId()); | |
1536 } | |
1537 | |
1538 { | |
1539 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); | |
1540 MutableEntry me(&wtrans, GET_BY_HANDLE, written_metahandle); | |
1541 ASSERT_TRUE(me.good()); | |
1542 me.PutIsDel(true); | |
1543 } | |
1544 | |
1545 // Test children ops after the children have been deleted. | |
1546 { | |
1547 ReadTransaction rtrans(FROM_HERE, dir().get()); | |
1548 Entry e(&rtrans, GET_BY_ID, id); | |
1549 ASSERT_TRUE(e.good()); | |
1550 | |
1551 Entry root(&rtrans, GET_BY_ID, rtrans.root_id()); | |
1552 ASSERT_TRUE(root.good()); | |
1553 EXPECT_FALSE(dir()->HasChildren(&rtrans, rtrans.root_id())); | |
1554 EXPECT_TRUE(root.GetFirstChildId().IsNull()); | |
1555 } | |
1556 | |
1557 dir()->SaveChanges(); | |
1558 } | |
1559 | |
1560 TEST_F(SyncableDirectoryTest, ClientIndexRebuildsProperly) { | |
1561 int64_t written_metahandle; | |
1562 TestIdFactory factory; | |
1563 const Id id = factory.NewServerId(); | |
1564 std::string name = "cheesepuffs"; | |
1565 std::string tag = "dietcoke"; | |
1566 | |
1567 // Test creating a new meta entry. | |
1568 { | |
1569 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); | |
1570 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), name); | |
1571 ASSERT_TRUE(me.good()); | |
1572 me.PutId(id); | |
1573 me.PutBaseVersion(1); | |
1574 me.PutUniqueClientTag(tag); | |
1575 written_metahandle = me.GetMetahandle(); | |
1576 } | |
1577 dir()->SaveChanges(); | |
1578 | |
1579 // Close and reopen, causing index regeneration. | |
1580 ReopenDirectory(); | |
1581 { | |
1582 ReadTransaction trans(FROM_HERE, dir().get()); | |
1583 Entry me(&trans, GET_BY_CLIENT_TAG, tag); | |
1584 ASSERT_TRUE(me.good()); | |
1585 EXPECT_EQ(me.GetId(), id); | |
1586 EXPECT_EQ(me.GetBaseVersion(), 1); | |
1587 EXPECT_EQ(me.GetUniqueClientTag(), tag); | |
1588 EXPECT_EQ(me.GetMetahandle(), written_metahandle); | |
1589 } | |
1590 } | |
1591 | |
1592 TEST_F(SyncableDirectoryTest, ClientIndexRebuildsDeletedProperly) { | |
1593 TestIdFactory factory; | |
1594 const Id id = factory.NewServerId(); | |
1595 std::string tag = "dietcoke"; | |
1596 | |
1597 // Test creating a deleted, unsynced, server meta entry. | |
1598 { | |
1599 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); | |
1600 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), "deleted"); | |
1601 ASSERT_TRUE(me.good()); | |
1602 me.PutId(id); | |
1603 me.PutBaseVersion(1); | |
1604 me.PutUniqueClientTag(tag); | |
1605 me.PutIsDel(true); | |
1606 me.PutIsUnsynced(true); // Or it might be purged. | |
1607 } | |
1608 dir()->SaveChanges(); | |
1609 | |
1610 // Close and reopen, causing index regeneration. | |
1611 ReopenDirectory(); | |
1612 { | |
1613 ReadTransaction trans(FROM_HERE, dir().get()); | |
1614 Entry me(&trans, GET_BY_CLIENT_TAG, tag); | |
1615 // Should still be present and valid in the client tag index. | |
1616 ASSERT_TRUE(me.good()); | |
1617 EXPECT_EQ(me.GetId(), id); | |
1618 EXPECT_EQ(me.GetUniqueClientTag(), tag); | |
1619 EXPECT_TRUE(me.GetIsDel()); | |
1620 EXPECT_TRUE(me.GetIsUnsynced()); | |
1621 } | |
1622 } | |
1623 | |
1624 TEST_F(SyncableDirectoryTest, ToValue) { | |
1625 const Id id = TestIdFactory::FromNumber(99); | |
1626 { | |
1627 ReadTransaction rtrans(FROM_HERE, dir().get()); | |
1628 Entry e(&rtrans, GET_BY_ID, id); | |
1629 EXPECT_FALSE(e.good()); // Hasn't been written yet. | |
1630 | |
1631 std::unique_ptr<base::DictionaryValue> value(e.ToValue(NULL)); | |
1632 ExpectDictBooleanValue(false, *value, "good"); | |
1633 EXPECT_EQ(1u, value->size()); | |
1634 } | |
1635 | |
1636 // Test creating a new meta entry. | |
1637 { | |
1638 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); | |
1639 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), "new"); | |
1640 ASSERT_TRUE(me.good()); | |
1641 me.PutId(id); | |
1642 me.PutBaseVersion(1); | |
1643 | |
1644 std::unique_ptr<base::DictionaryValue> value(me.ToValue(NULL)); | |
1645 ExpectDictBooleanValue(true, *value, "good"); | |
1646 EXPECT_TRUE(value->HasKey("kernel")); | |
1647 ExpectDictStringValue("Bookmarks", *value, "modelType"); | |
1648 ExpectDictBooleanValue(true, *value, "existsOnClientBecauseNameIsNonEmpty"); | |
1649 ExpectDictBooleanValue(false, *value, "isRoot"); | |
1650 } | |
1651 | |
1652 dir()->SaveChanges(); | |
1653 } | |
1654 | |
1655 // A thread that creates a bunch of directory entries. | |
1656 class StressTransactionsDelegate : public base::PlatformThread::Delegate { | |
1657 public: | |
1658 StressTransactionsDelegate(Directory* dir, int thread_number) | |
1659 : dir_(dir), thread_number_(thread_number) {} | |
1660 | |
1661 private: | |
1662 Directory* const dir_; | |
1663 const int thread_number_; | |
1664 | |
1665 // PlatformThread::Delegate methods: | |
1666 void ThreadMain() override { | |
1667 int entry_count = 0; | |
1668 std::string path_name; | |
1669 | |
1670 for (int i = 0; i < 20; ++i) { | |
1671 const int rand_action = base::RandInt(0, 9); | |
1672 if (rand_action < 4 && !path_name.empty()) { | |
1673 ReadTransaction trans(FROM_HERE, dir_); | |
1674 EXPECT_EQ(1, CountEntriesWithName(&trans, trans.root_id(), path_name)); | |
1675 base::PlatformThread::Sleep( | |
1676 base::TimeDelta::FromMilliseconds(base::RandInt(0, 9))); | |
1677 } else { | |
1678 std::string unique_name = | |
1679 base::StringPrintf("%d.%d", thread_number_, entry_count++); | |
1680 path_name.assign(unique_name.begin(), unique_name.end()); | |
1681 WriteTransaction trans(FROM_HERE, UNITTEST, dir_); | |
1682 MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), path_name); | |
1683 EXPECT_TRUE(e.good()); | |
1684 base::PlatformThread::Sleep( | |
1685 base::TimeDelta::FromMilliseconds(base::RandInt(0, 19))); | |
1686 e.PutIsUnsynced(true); | |
1687 if (e.PutId(TestIdFactory::FromNumber(base::RandInt(0, RAND_MAX))) && | |
1688 e.GetId().ServerKnows() && !e.GetId().IsRoot()) { | |
1689 e.PutBaseVersion(1); | |
1690 } | |
1691 } | |
1692 } | |
1693 } | |
1694 | |
1695 DISALLOW_COPY_AND_ASSIGN(StressTransactionsDelegate); | |
1696 }; | |
1697 | |
1698 // Stress test Directory by accessing it from several threads concurrently. | |
1699 TEST_F(SyncableDirectoryTest, StressTransactions) { | |
1700 const int kThreadCount = 7; | |
1701 base::PlatformThreadHandle threads[kThreadCount]; | |
1702 std::unique_ptr<StressTransactionsDelegate> thread_delegates[kThreadCount]; | |
1703 | |
1704 for (int i = 0; i < kThreadCount; ++i) { | |
1705 thread_delegates[i].reset(new StressTransactionsDelegate(dir().get(), i)); | |
1706 ASSERT_TRUE(base::PlatformThread::Create( | |
1707 0, thread_delegates[i].get(), &threads[i])); | |
1708 } | |
1709 | |
1710 for (int i = 0; i < kThreadCount; ++i) { | |
1711 base::PlatformThread::Join(threads[i]); | |
1712 } | |
1713 } | |
1714 | |
1715 // Verify that Directory is notifed when a MutableEntry's AttachmentMetadata | |
1716 // changes. | |
1717 TEST_F(SyncableDirectoryTest, MutableEntry_PutAttachmentMetadata) { | |
1718 sync_pb::AttachmentMetadata attachment_metadata; | |
1719 sync_pb::AttachmentMetadataRecord* record = attachment_metadata.add_record(); | |
1720 sync_pb::AttachmentIdProto attachment_id_proto = | |
1721 syncer::CreateAttachmentIdProto(0, 0); | |
1722 *record->mutable_id() = attachment_id_proto; | |
1723 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto)); | |
1724 { | |
1725 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1726 | |
1727 // Create an entry with attachment metadata and see that the attachment id | |
1728 // is not linked. | |
1729 MutableEntry entry( | |
1730 &trans, CREATE, PREFERENCES, trans.root_id(), "some entry"); | |
1731 entry.PutId(TestIdFactory::FromNumber(-1)); | |
1732 entry.PutIsUnsynced(true); | |
1733 | |
1734 Directory::Metahandles metahandles; | |
1735 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto)); | |
1736 dir()->GetMetahandlesByAttachmentId( | |
1737 &trans, attachment_id_proto, &metahandles); | |
1738 ASSERT_TRUE(metahandles.empty()); | |
1739 | |
1740 // Now add the attachment metadata and see that Directory believes it is | |
1741 // linked. | |
1742 entry.PutAttachmentMetadata(attachment_metadata); | |
1743 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto)); | |
1744 dir()->GetMetahandlesByAttachmentId( | |
1745 &trans, attachment_id_proto, &metahandles); | |
1746 ASSERT_FALSE(metahandles.empty()); | |
1747 ASSERT_EQ(metahandles[0], entry.GetMetahandle()); | |
1748 | |
1749 // Clear out the attachment metadata and see that it's no longer linked. | |
1750 sync_pb::AttachmentMetadata empty_attachment_metadata; | |
1751 entry.PutAttachmentMetadata(empty_attachment_metadata); | |
1752 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto)); | |
1753 dir()->GetMetahandlesByAttachmentId( | |
1754 &trans, attachment_id_proto, &metahandles); | |
1755 ASSERT_TRUE(metahandles.empty()); | |
1756 } | |
1757 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto)); | |
1758 } | |
1759 | |
1760 // Verify that UpdateAttachmentId updates attachment_id and is_on_server flag. | |
1761 TEST_F(SyncableDirectoryTest, MutableEntry_UpdateAttachmentId) { | |
1762 sync_pb::AttachmentMetadata attachment_metadata; | |
1763 sync_pb::AttachmentMetadataRecord* r1 = attachment_metadata.add_record(); | |
1764 sync_pb::AttachmentMetadataRecord* r2 = attachment_metadata.add_record(); | |
1765 *r1->mutable_id() = syncer::CreateAttachmentIdProto(0, 0); | |
1766 *r2->mutable_id() = syncer::CreateAttachmentIdProto(0, 0); | |
1767 sync_pb::AttachmentIdProto attachment_id_proto = r1->id(); | |
1768 | |
1769 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1770 | |
1771 MutableEntry entry( | |
1772 &trans, CREATE, PREFERENCES, trans.root_id(), "some entry"); | |
1773 entry.PutId(TestIdFactory::FromNumber(-1)); | |
1774 entry.PutAttachmentMetadata(attachment_metadata); | |
1775 | |
1776 { | |
1777 const sync_pb::AttachmentMetadata& entry_metadata = | |
1778 entry.GetAttachmentMetadata(); | |
1779 ASSERT_EQ(2, entry_metadata.record_size()); | |
1780 ASSERT_FALSE(entry_metadata.record(0).is_on_server()); | |
1781 ASSERT_FALSE(entry_metadata.record(1).is_on_server()); | |
1782 ASSERT_FALSE(entry.GetIsUnsynced()); | |
1783 } | |
1784 | |
1785 entry.MarkAttachmentAsOnServer(attachment_id_proto); | |
1786 | |
1787 { | |
1788 // Re-get entry_metadata because it is immutable in the directory and | |
1789 // entry_metadata reference has been made invalid by | |
1790 // MarkAttachmentAsOnServer call above. | |
1791 const sync_pb::AttachmentMetadata& entry_metadata = | |
1792 entry.GetAttachmentMetadata(); | |
1793 ASSERT_TRUE(entry_metadata.record(0).is_on_server()); | |
1794 ASSERT_FALSE(entry_metadata.record(1).is_on_server()); | |
1795 ASSERT_TRUE(entry.GetIsUnsynced()); | |
1796 } | |
1797 } | |
1798 | |
1799 // Verify that deleted entries with attachments will retain the attachments. | |
1800 TEST_F(SyncableDirectoryTest, Directory_DeleteDoesNotUnlinkAttachments) { | |
1801 sync_pb::AttachmentMetadata attachment_metadata; | |
1802 sync_pb::AttachmentMetadataRecord* record = attachment_metadata.add_record(); | |
1803 sync_pb::AttachmentIdProto attachment_id_proto = | |
1804 syncer::CreateAttachmentIdProto(0, 0); | |
1805 *record->mutable_id() = attachment_id_proto; | |
1806 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto)); | |
1807 const Id id = TestIdFactory::FromNumber(-1); | |
1808 | |
1809 // Create an entry with attachment metadata and see that the attachment id | |
1810 // is linked. | |
1811 CreateEntryWithAttachmentMetadata( | |
1812 PREFERENCES, "some entry", id, attachment_metadata); | |
1813 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto)); | |
1814 | |
1815 // Delete the entry and see that it's still linked because the entry hasn't | |
1816 // yet been purged. | |
1817 DeleteEntry(id); | |
1818 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto)); | |
1819 | |
1820 // Reload the Directory, purging the deleted entry, and see that the | |
1821 // attachment is no longer linked. | |
1822 SimulateSaveAndReloadDir(); | |
1823 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto)); | |
1824 } | |
1825 | |
1826 // Verify that a given attachment can be referenced by multiple entries and that | |
1827 // any one of the references is sufficient to ensure it remains linked. | |
1828 TEST_F(SyncableDirectoryTest, Directory_LastReferenceUnlinksAttachments) { | |
1829 // Create one attachment. | |
1830 sync_pb::AttachmentMetadata attachment_metadata; | |
1831 sync_pb::AttachmentMetadataRecord* record = attachment_metadata.add_record(); | |
1832 sync_pb::AttachmentIdProto attachment_id_proto = | |
1833 syncer::CreateAttachmentIdProto(0, 0); | |
1834 *record->mutable_id() = attachment_id_proto; | |
1835 | |
1836 // Create two entries, each referencing the attachment. | |
1837 const Id id1 = TestIdFactory::FromNumber(-1); | |
1838 const Id id2 = TestIdFactory::FromNumber(-2); | |
1839 CreateEntryWithAttachmentMetadata( | |
1840 PREFERENCES, "some entry", id1, attachment_metadata); | |
1841 CreateEntryWithAttachmentMetadata( | |
1842 PREFERENCES, "some other entry", id2, attachment_metadata); | |
1843 | |
1844 // See that the attachment is considered linked. | |
1845 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto)); | |
1846 | |
1847 // Delete the first entry, reload the Directory, see that the attachment is | |
1848 // still linked. | |
1849 DeleteEntry(id1); | |
1850 SimulateSaveAndReloadDir(); | |
1851 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto)); | |
1852 | |
1853 // Delete the second entry, reload the Directory, see that the attachment is | |
1854 // no loner linked. | |
1855 DeleteEntry(id2); | |
1856 SimulateSaveAndReloadDir(); | |
1857 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto)); | |
1858 } | |
1859 | |
1860 TEST_F(SyncableDirectoryTest, Directory_GetAttachmentIdsToUpload) { | |
1861 // Create one attachment, referenced by two entries. | |
1862 AttachmentId attachment_id = AttachmentId::Create(0, 0); | |
1863 sync_pb::AttachmentIdProto attachment_id_proto = attachment_id.GetProto(); | |
1864 sync_pb::AttachmentMetadata attachment_metadata; | |
1865 sync_pb::AttachmentMetadataRecord* record = attachment_metadata.add_record(); | |
1866 *record->mutable_id() = attachment_id_proto; | |
1867 const Id id1 = TestIdFactory::FromNumber(-1); | |
1868 const Id id2 = TestIdFactory::FromNumber(-2); | |
1869 CreateEntryWithAttachmentMetadata( | |
1870 PREFERENCES, "some entry", id1, attachment_metadata); | |
1871 CreateEntryWithAttachmentMetadata( | |
1872 PREFERENCES, "some other entry", id2, attachment_metadata); | |
1873 | |
1874 // See that Directory reports that this attachment is not on the server. | |
1875 AttachmentIdList ids; | |
1876 { | |
1877 ReadTransaction trans(FROM_HERE, dir().get()); | |
1878 dir()->GetAttachmentIdsToUpload(&trans, PREFERENCES, &ids); | |
1879 } | |
1880 ASSERT_EQ(1U, ids.size()); | |
1881 ASSERT_EQ(attachment_id, *ids.begin()); | |
1882 | |
1883 // Call again, but this time with a ModelType for which there are no entries. | |
1884 // See that Directory correctly reports that there are none. | |
1885 { | |
1886 ReadTransaction trans(FROM_HERE, dir().get()); | |
1887 dir()->GetAttachmentIdsToUpload(&trans, PASSWORDS, &ids); | |
1888 } | |
1889 ASSERT_TRUE(ids.empty()); | |
1890 | |
1891 // Now, mark the attachment as "on the server" via entry_1. | |
1892 { | |
1893 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1894 MutableEntry entry_1(&trans, GET_BY_ID, id1); | |
1895 entry_1.MarkAttachmentAsOnServer(attachment_id_proto); | |
1896 } | |
1897 | |
1898 // See that Directory no longer reports that this attachment is not on the | |
1899 // server. | |
1900 { | |
1901 ReadTransaction trans(FROM_HERE, dir().get()); | |
1902 dir()->GetAttachmentIdsToUpload(&trans, PREFERENCES, &ids); | |
1903 } | |
1904 ASSERT_TRUE(ids.empty()); | |
1905 } | |
1906 | |
1907 // Verify that the directory accepts entries with unset parent ID. | |
1908 TEST_F(SyncableDirectoryTest, MutableEntry_ImplicitParentId) { | |
1909 TestIdFactory id_factory; | |
1910 const Id root_id = TestIdFactory::root(); | |
1911 const Id p_root_id = id_factory.NewServerId(); | |
1912 const Id a_root_id = id_factory.NewServerId(); | |
1913 const Id item1_id = id_factory.NewServerId(); | |
1914 const Id item2_id = id_factory.NewServerId(); | |
1915 const Id item3_id = id_factory.NewServerId(); | |
1916 // Create two type root folders that are necessary (for now) | |
1917 // for creating items without explicitly set Parent ID | |
1918 { | |
1919 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1920 MutableEntry p_root(&trans, CREATE, PREFERENCES, root_id, "P"); | |
1921 ASSERT_TRUE(p_root.good()); | |
1922 p_root.PutIsDir(true); | |
1923 p_root.PutId(p_root_id); | |
1924 p_root.PutBaseVersion(1); | |
1925 | |
1926 MutableEntry a_root(&trans, CREATE, AUTOFILL, root_id, "A"); | |
1927 ASSERT_TRUE(a_root.good()); | |
1928 a_root.PutIsDir(true); | |
1929 a_root.PutId(a_root_id); | |
1930 a_root.PutBaseVersion(1); | |
1931 } | |
1932 | |
1933 // Create two entries with implicit parent nodes and one entry with explicit | |
1934 // parent node. | |
1935 { | |
1936 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1937 MutableEntry item1(&trans, CREATE, PREFERENCES, "P1"); | |
1938 item1.PutBaseVersion(1); | |
1939 item1.PutId(item1_id); | |
1940 MutableEntry item2(&trans, CREATE, AUTOFILL, "A1"); | |
1941 item2.PutBaseVersion(1); | |
1942 item2.PutId(item2_id); | |
1943 // Placing an AUTOFILL item under the root isn't expected, | |
1944 // but let's test it to verify that explicit root overrides the implicit | |
1945 // one and this entry doesn't end up under the "A" root. | |
1946 MutableEntry item3(&trans, CREATE, AUTOFILL, root_id, "A2"); | |
1947 item3.PutBaseVersion(1); | |
1948 item3.PutId(item3_id); | |
1949 } | |
1950 | |
1951 { | |
1952 ReadTransaction trans(FROM_HERE, dir().get()); | |
1953 // Verify that item1 and item2 are good and have no ParentId. | |
1954 Entry item1(&trans, GET_BY_ID, item1_id); | |
1955 ASSERT_TRUE(item1.good()); | |
1956 ASSERT_TRUE(item1.GetParentId().IsNull()); | |
1957 Entry item2(&trans, GET_BY_ID, item2_id); | |
1958 ASSERT_TRUE(item2.good()); | |
1959 ASSERT_TRUE(item2.GetParentId().IsNull()); | |
1960 // Verify that p_root and a_root have exactly one child each | |
1961 // (subtract one to exclude roots themselves). | |
1962 Entry p_root(&trans, GET_BY_ID, p_root_id); | |
1963 ASSERT_EQ(item1_id, p_root.GetFirstChildId()); | |
1964 ASSERT_EQ(1, p_root.GetTotalNodeCount() - 1); | |
1965 Entry a_root(&trans, GET_BY_ID, a_root_id); | |
1966 ASSERT_EQ(item2_id, a_root.GetFirstChildId()); | |
1967 ASSERT_EQ(1, a_root.GetTotalNodeCount() - 1); | |
1968 } | |
1969 } | |
1970 | |
1971 // Verify that the successor / predecessor navigation still works for | |
1972 // directory entries with unset Parent IDs. | |
1973 TEST_F(SyncableDirectoryTest, MutableEntry_ImplicitParentId_Siblings) { | |
1974 TestIdFactory id_factory; | |
1975 const Id root_id = TestIdFactory::root(); | |
1976 const Id p_root_id = id_factory.NewServerId(); | |
1977 const Id item1_id = id_factory.FromNumber(1); | |
1978 const Id item2_id = id_factory.FromNumber(2); | |
1979 | |
1980 // Create type root folder for PREFERENCES. | |
1981 { | |
1982 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1983 MutableEntry p_root(&trans, CREATE, PREFERENCES, root_id, "P"); | |
1984 ASSERT_TRUE(p_root.good()); | |
1985 p_root.PutIsDir(true); | |
1986 p_root.PutId(p_root_id); | |
1987 p_root.PutBaseVersion(1); | |
1988 } | |
1989 | |
1990 // Create two PREFERENCES entries with implicit parent nodes. | |
1991 { | |
1992 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
1993 MutableEntry item1(&trans, CREATE, PREFERENCES, "P1"); | |
1994 item1.PutBaseVersion(1); | |
1995 item1.PutId(item1_id); | |
1996 MutableEntry item2(&trans, CREATE, PREFERENCES, "P2"); | |
1997 item2.PutBaseVersion(1); | |
1998 item2.PutId(item2_id); | |
1999 } | |
2000 | |
2001 // Verify GetSuccessorId and GetPredecessorId calls for these items. | |
2002 // Please note that items are sorted according to their ID, e.g. | |
2003 // item1 first, then item2. | |
2004 { | |
2005 ReadTransaction trans(FROM_HERE, dir().get()); | |
2006 Entry item1(&trans, GET_BY_ID, item1_id); | |
2007 EXPECT_EQ(Id(), item1.GetPredecessorId()); | |
2008 EXPECT_EQ(item2_id, item1.GetSuccessorId()); | |
2009 | |
2010 Entry item2(&trans, GET_BY_ID, item2_id); | |
2011 EXPECT_EQ(item1_id, item2.GetPredecessorId()); | |
2012 EXPECT_EQ(Id(), item2.GetSuccessorId()); | |
2013 } | |
2014 } | |
2015 | |
2016 TEST_F(SyncableDirectoryTest, SaveChangesSnapshot_HasUnsavedMetahandleChanges) { | |
2017 EntryKernel kernel; | |
2018 Directory::SaveChangesSnapshot snapshot; | |
2019 EXPECT_FALSE(snapshot.HasUnsavedMetahandleChanges()); | |
2020 snapshot.dirty_metas.insert(&kernel); | |
2021 EXPECT_TRUE(snapshot.HasUnsavedMetahandleChanges()); | |
2022 snapshot.dirty_metas.clear(); | |
2023 | |
2024 EXPECT_FALSE(snapshot.HasUnsavedMetahandleChanges()); | |
2025 snapshot.metahandles_to_purge.insert(1); | |
2026 EXPECT_TRUE(snapshot.HasUnsavedMetahandleChanges()); | |
2027 snapshot.metahandles_to_purge.clear(); | |
2028 | |
2029 EXPECT_FALSE(snapshot.HasUnsavedMetahandleChanges()); | |
2030 snapshot.delete_journals.insert(&kernel); | |
2031 EXPECT_TRUE(snapshot.HasUnsavedMetahandleChanges()); | |
2032 snapshot.delete_journals.clear(); | |
2033 | |
2034 EXPECT_FALSE(snapshot.HasUnsavedMetahandleChanges()); | |
2035 snapshot.delete_journals_to_purge.insert(1); | |
2036 EXPECT_TRUE(snapshot.HasUnsavedMetahandleChanges()); | |
2037 snapshot.delete_journals_to_purge.clear(); | |
2038 } | |
2039 | |
2040 // Verify that Directory triggers an unrecoverable error when a catastrophic | |
2041 // DirectoryBackingStore error is detected. | |
2042 TEST_F(SyncableDirectoryTest, CatastrophicError) { | |
2043 MockUnrecoverableErrorHandler unrecoverable_error_handler; | |
2044 Directory dir(new InMemoryDirectoryBackingStore("catastrophic_error"), | |
2045 MakeWeakHandle(unrecoverable_error_handler.GetWeakPtr()), | |
2046 base::Closure(), nullptr, nullptr); | |
2047 ASSERT_EQ(OPENED, dir.Open(kDirectoryName, directory_change_delegate(), | |
2048 NullTransactionObserver())); | |
2049 ASSERT_EQ(0, unrecoverable_error_handler.invocation_count()); | |
2050 | |
2051 // Fire off two catastrophic errors. Call it twice to ensure Directory is | |
2052 // tolerant of multiple invocations since that may happen in the real world. | |
2053 dir.OnCatastrophicError(); | |
2054 dir.OnCatastrophicError(); | |
2055 | |
2056 base::RunLoop().RunUntilIdle(); | |
2057 | |
2058 // See that the unrecoverable error handler has been invoked twice. | |
2059 ASSERT_EQ(2, unrecoverable_error_handler.invocation_count()); | |
2060 } | |
2061 | |
2062 bool EntitySpecificsValuesAreSame(const sync_pb::EntitySpecifics& v1, | |
2063 const sync_pb::EntitySpecifics& v2) { | |
2064 return &v1 == &v2; | |
2065 } | |
2066 | |
2067 // Verifies that server and client specifics are shared when their values | |
2068 // are equal. | |
2069 TEST_F(SyncableDirectoryTest, SharingOfClientAndServerSpecifics) { | |
2070 sync_pb::EntitySpecifics specifics1; | |
2071 sync_pb::EntitySpecifics specifics2; | |
2072 sync_pb::EntitySpecifics specifics3; | |
2073 AddDefaultFieldValue(BOOKMARKS, &specifics1); | |
2074 AddDefaultFieldValue(BOOKMARKS, &specifics2); | |
2075 AddDefaultFieldValue(BOOKMARKS, &specifics3); | |
2076 specifics1.mutable_bookmark()->set_url("foo"); | |
2077 specifics2.mutable_bookmark()->set_url("bar"); | |
2078 // specifics3 has the same URL as specifics1 | |
2079 specifics3.mutable_bookmark()->set_url("foo"); | |
2080 | |
2081 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
2082 MutableEntry item(&trans, CREATE, BOOKMARKS, trans.root_id(), "item"); | |
2083 item.PutId(TestIdFactory::FromNumber(1)); | |
2084 item.PutBaseVersion(10); | |
2085 | |
2086 // Verify sharing. | |
2087 item.PutSpecifics(specifics1); | |
2088 item.PutServerSpecifics(specifics1); | |
2089 EXPECT_TRUE(EntitySpecificsValuesAreSame(item.GetSpecifics(), | |
2090 item.GetServerSpecifics())); | |
2091 | |
2092 // Verify that specifics are no longer shared. | |
2093 item.PutServerSpecifics(specifics2); | |
2094 EXPECT_FALSE(EntitySpecificsValuesAreSame(item.GetSpecifics(), | |
2095 item.GetServerSpecifics())); | |
2096 | |
2097 // Verify that specifics are shared again because specifics3 matches | |
2098 // specifics1. | |
2099 item.PutServerSpecifics(specifics3); | |
2100 EXPECT_TRUE(EntitySpecificsValuesAreSame(item.GetSpecifics(), | |
2101 item.GetServerSpecifics())); | |
2102 | |
2103 // Verify that copying the same value back to SPECIFICS is still OK. | |
2104 item.PutSpecifics(specifics3); | |
2105 EXPECT_TRUE(EntitySpecificsValuesAreSame(item.GetSpecifics(), | |
2106 item.GetServerSpecifics())); | |
2107 | |
2108 // Verify sharing with BASE_SERVER_SPECIFICS. | |
2109 EXPECT_FALSE(EntitySpecificsValuesAreSame(item.GetServerSpecifics(), | |
2110 item.GetBaseServerSpecifics())); | |
2111 item.PutBaseServerSpecifics(specifics3); | |
2112 EXPECT_TRUE(EntitySpecificsValuesAreSame(item.GetServerSpecifics(), | |
2113 item.GetBaseServerSpecifics())); | |
2114 } | |
2115 | |
2116 // Tests checking and marking a type as having its initial sync completed. | |
2117 TEST_F(SyncableDirectoryTest, InitialSyncEndedForType) { | |
2118 // Not completed if there is no root node. | |
2119 EXPECT_FALSE(dir()->InitialSyncEndedForType(PREFERENCES)); | |
2120 | |
2121 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); | |
2122 // Create the root node. | |
2123 ModelNeutralMutableEntry entry(&trans, syncable::CREATE_NEW_TYPE_ROOT, | |
2124 PREFERENCES); | |
2125 ASSERT_TRUE(entry.good()); | |
2126 | |
2127 entry.PutServerIsDir(true); | |
2128 entry.PutUniqueServerTag(ModelTypeToRootTag(PREFERENCES)); | |
2129 | |
2130 // Should still be marked as incomplete. | |
2131 EXPECT_FALSE(dir()->InitialSyncEndedForType(&trans, PREFERENCES)); | |
2132 | |
2133 // Mark as complete and verify. | |
2134 dir()->MarkInitialSyncEndedForType(&trans, PREFERENCES); | |
2135 EXPECT_TRUE(dir()->InitialSyncEndedForType(&trans, PREFERENCES)); | |
2136 } | |
2137 | |
2138 } // namespace syncable | |
2139 | |
2140 } // namespace syncer | |
OLD | NEW |