1 // Copyright 2012 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 <string>
6
7 #include "base/basictypes.h"
8 #include "base/compiler_specific.h"
9 #include "base/file_util.h"
10 #include "base/files/file_path.h"
11 #include "base/files/scoped_temp_dir.h"
12 #include "base/location.h"
13 #include "base/logging.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/stl_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/synchronization/condition_variable.h"
19 #include "base/test/values_test_util.h"
20 #include "base/threading/platform_thread.h"
21 #include "base/values.h"
22 #include "sync/protocol/bookmark_specifics.pb.h"
23 #include "sync/syncable/directory_backing_store.h"
24 #include "sync/syncable/directory_change_delegate.h"
25 #include "sync/syncable/in_memory_directory_backing_store.h"
26 #include "sync/syncable/metahandle_set.h"
27 #include "sync/syncable/mutable_entry.h"
28 #include "sync/syncable/on_disk_directory_backing_store.h"
29 #include "sync/syncable/syncable_proto_util.h"
30 #include "sync/syncable/syncable_read_transaction.h"
31 #include "sync/syncable/syncable_util.h"
32 #include "sync/syncable/syncable_write_transaction.h"
33 #include "sync/test/engine/test_id_factory.h"
34 #include "sync/test/engine/test_syncable_utils.h"
35 #include "sync/test/fake_encryptor.h"
36 #include "sync/test/null_directory_change_delegate.h"
37 #include "sync/test/null_transaction_observer.h"
38 #include "sync/util/test_unrecoverable_error_handler.h"
39 #include "testing/gtest/include/gtest/gtest.h"
40
41 namespace syncer {
42 namespace syncable {
43
44 using base::ExpectDictBooleanValue;
45 using base::ExpectDictStringValue;
46
47 class SyncableKernelTest : public testing::Test {};
48
49 // TODO(akalin): Add unit tests for EntryKernel::ContainsString().
50
TEST_F(SyncableKernelTest,ToValue)51 TEST_F(SyncableKernelTest, ToValue) {
52 EntryKernel kernel;
53 scoped_ptr<base::DictionaryValue> value(kernel.ToValue(NULL));
54 if (value) {
55 // Not much to check without repeating the ToValue() code.
56 EXPECT_TRUE(value->HasKey("isDirty"));
57 // The extra +2 is for "isDirty" and "serverModelType".
58 EXPECT_EQ(BIT_TEMPS_END - BEGIN_FIELDS + 2,
59 static_cast<int>(value->size()));
60 } else {
61 ADD_FAILURE();
62 }
63 }
64
65 namespace {
PutDataAsBookmarkFavicon(WriteTransaction * wtrans,MutableEntry * e,const char * bytes,size_t bytes_length)66 void PutDataAsBookmarkFavicon(WriteTransaction* wtrans,
67 MutableEntry* e,
68 const char* bytes,
69 size_t bytes_length) {
70 sync_pb::EntitySpecifics specifics;
71 specifics.mutable_bookmark()->set_url("http://demo/");
72 specifics.mutable_bookmark()->set_favicon(bytes, bytes_length);
73 e->PutSpecifics(specifics);
74 }
75
ExpectDataFromBookmarkFaviconEquals(BaseTransaction * trans,Entry * e,const char * bytes,size_t bytes_length)76 void ExpectDataFromBookmarkFaviconEquals(BaseTransaction* trans,
77 Entry* e,
78 const char* bytes,
79 size_t bytes_length) {
80 ASSERT_TRUE(e->good());
81 ASSERT_TRUE(e->GetSpecifics().has_bookmark());
82 ASSERT_EQ("http://demo/", e->GetSpecifics().bookmark().url());
83 ASSERT_EQ(std::string(bytes, bytes_length),
84 e->GetSpecifics().bookmark().favicon());
85 }
86 } // namespace
87
88 class SyncableGeneralTest : public testing::Test {
89 public:
90 static const char kIndexTestName[];
SetUp()91 virtual void SetUp() {
92 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
93 db_path_ = temp_dir_.path().Append(
94 FILE_PATH_LITERAL("SyncableTest.sqlite3"));
95 }
96
TearDown()97 virtual void TearDown() {
98 }
99 protected:
100 base::MessageLoop message_loop_;
101 base::ScopedTempDir temp_dir_;
102 NullDirectoryChangeDelegate delegate_;
103 FakeEncryptor encryptor_;
104 TestUnrecoverableErrorHandler handler_;
105 base::FilePath db_path_;
106 };
107
108 const char SyncableGeneralTest::kIndexTestName[] = "IndexTest";
109
TEST_F(SyncableGeneralTest,General)110 TEST_F(SyncableGeneralTest, General) {
111 Directory dir(new InMemoryDirectoryBackingStore("SimpleTest"),
112 &handler_,
113 NULL,
114 NULL,
115 NULL);
116
117 ASSERT_EQ(OPENED, dir.Open(
118 "SimpleTest", &delegate_, NullTransactionObserver()));
119
120 int64 root_metahandle;
121 {
122 ReadTransaction rtrans(FROM_HERE, &dir);
123 Entry e(&rtrans, GET_BY_ID, rtrans.root_id());
124 ASSERT_TRUE(e.good());
125 root_metahandle = e.GetMetahandle();
126 }
127
128 int64 written_metahandle;
129 const Id id = TestIdFactory::FromNumber(99);
130 std::string name = "Jeff";
131 // Test simple read operations on an empty DB.
132 {
133 ReadTransaction rtrans(FROM_HERE, &dir);
134 Entry e(&rtrans, GET_BY_ID, id);
135 ASSERT_FALSE(e.good()); // Hasn't been written yet.
136
137 Directory::Metahandles child_handles;
138 dir.GetChildHandlesById(&rtrans, rtrans.root_id(), &child_handles);
139 EXPECT_TRUE(child_handles.empty());
140
141 dir.GetChildHandlesByHandle(&rtrans, root_metahandle, &child_handles);
142 EXPECT_TRUE(child_handles.empty());
143 }
144
145 // Test creating a new meta entry.
146 {
147 WriteTransaction wtrans(FROM_HERE, UNITTEST, &dir);
148 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), name);
149 ASSERT_TRUE(me.good());
150 me.PutId(id);
151 me.PutBaseVersion(1);
152 written_metahandle = me.GetMetahandle();
153 }
154
155 // Test GetChildHandles* after something is now in the DB.
156 // Also check that GET_BY_ID works.
157 {
158 ReadTransaction rtrans(FROM_HERE, &dir);
159 Entry e(&rtrans, GET_BY_ID, id);
160 ASSERT_TRUE(e.good());
161
162 Directory::Metahandles child_handles;
163 dir.GetChildHandlesById(&rtrans, rtrans.root_id(), &child_handles);
164 EXPECT_EQ(1u, child_handles.size());
165
166 for (Directory::Metahandles::iterator i = child_handles.begin();
167 i != child_handles.end(); ++i) {
168 EXPECT_EQ(*i, written_metahandle);
169 }
170
171 dir.GetChildHandlesByHandle(&rtrans, root_metahandle, &child_handles);
172 EXPECT_EQ(1u, child_handles.size());
173
174 for (Directory::Metahandles::iterator i = child_handles.begin();
175 i != child_handles.end(); ++i) {
176 EXPECT_EQ(*i, written_metahandle);
177 }
178 }
179
180 // Test writing data to an entity. Also check that GET_BY_HANDLE works.
181 static const char s[] = "Hello World.";
182 {
183 WriteTransaction trans(FROM_HERE, UNITTEST, &dir);
184 MutableEntry e(&trans, GET_BY_HANDLE, written_metahandle);
185 ASSERT_TRUE(e.good());
186 PutDataAsBookmarkFavicon(&trans, &e, s, sizeof(s));
187 }
188
189 // Test reading back the contents that we just wrote.
190 {
191 WriteTransaction trans(FROM_HERE, UNITTEST, &dir);
192 MutableEntry e(&trans, GET_BY_HANDLE, written_metahandle);
193 ASSERT_TRUE(e.good());
194 ExpectDataFromBookmarkFaviconEquals(&trans, &e, s, sizeof(s));
195 }
196
197 // Verify it exists in the folder.
198 {
199 ReadTransaction rtrans(FROM_HERE, &dir);
200 EXPECT_EQ(1, CountEntriesWithName(&rtrans, rtrans.root_id(), name));
201 }
202
203 // Now delete it.
204 {
205 WriteTransaction trans(FROM_HERE, UNITTEST, &dir);
206 MutableEntry e(&trans, GET_BY_HANDLE, written_metahandle);
207 e.PutIsDel(true);
208
209 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), name));
210 }
211
212 dir.SaveChanges();
213 }
214
TEST_F(SyncableGeneralTest,ChildrenOps)215 TEST_F(SyncableGeneralTest, ChildrenOps) {
216 Directory dir(new InMemoryDirectoryBackingStore("SimpleTest"),
217 &handler_,
218 NULL,
219 NULL,
220 NULL);
221 ASSERT_EQ(OPENED, dir.Open(
222 "SimpleTest", &delegate_, NullTransactionObserver()));
223
224 int64 written_metahandle;
225 const Id id = TestIdFactory::FromNumber(99);
226 std::string name = "Jeff";
227 {
228 ReadTransaction rtrans(FROM_HERE, &dir);
229 Entry e(&rtrans, GET_BY_ID, id);
230 ASSERT_FALSE(e.good()); // Hasn't been written yet.
231
232 Entry root(&rtrans, GET_BY_ID, rtrans.root_id());
233 ASSERT_TRUE(root.good());
234 EXPECT_FALSE(dir.HasChildren(&rtrans, rtrans.root_id()));
235 EXPECT_TRUE(root.GetFirstChildId().IsRoot());
236 }
237
238 {
239 WriteTransaction wtrans(FROM_HERE, UNITTEST, &dir);
240 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), name);
241 ASSERT_TRUE(me.good());
242 me.PutId(id);
243 me.PutBaseVersion(1);
244 written_metahandle = me.GetMetahandle();
245 }
246
247 // Test children ops after something is now in the DB.
248 {
249 ReadTransaction rtrans(FROM_HERE, &dir);
250 Entry e(&rtrans, GET_BY_ID, id);
251 ASSERT_TRUE(e.good());
252
253 Entry child(&rtrans, GET_BY_HANDLE, written_metahandle);
254 ASSERT_TRUE(child.good());
255
256 Entry root(&rtrans, GET_BY_ID, rtrans.root_id());
257 ASSERT_TRUE(root.good());
258 EXPECT_TRUE(dir.HasChildren(&rtrans, rtrans.root_id()));
259 EXPECT_EQ(e.GetId(), root.GetFirstChildId());
260 }
261
262 {
263 WriteTransaction wtrans(FROM_HERE, UNITTEST, &dir);
264 MutableEntry me(&wtrans, GET_BY_HANDLE, written_metahandle);
265 ASSERT_TRUE(me.good());
266 me.PutIsDel(true);
267 }
268
269 // Test children ops after the children have been deleted.
270 {
271 ReadTransaction rtrans(FROM_HERE, &dir);
272 Entry e(&rtrans, GET_BY_ID, id);
273 ASSERT_TRUE(e.good());
274
275 Entry root(&rtrans, GET_BY_ID, rtrans.root_id());
276 ASSERT_TRUE(root.good());
277 EXPECT_FALSE(dir.HasChildren(&rtrans, rtrans.root_id()));
278 EXPECT_TRUE(root.GetFirstChildId().IsRoot());
279 }
280
281 dir.SaveChanges();
282 }
283
TEST_F(SyncableGeneralTest,ClientIndexRebuildsProperly)284 TEST_F(SyncableGeneralTest, ClientIndexRebuildsProperly) {
285 int64 written_metahandle;
286 TestIdFactory factory;
287 const Id id = factory.NewServerId();
288 std::string name = "cheesepuffs";
289 std::string tag = "dietcoke";
290
291 // Test creating a new meta entry.
292 {
293 Directory dir(new OnDiskDirectoryBackingStore(kIndexTestName, db_path_),
294 &handler_,
295 NULL,
296 NULL,
297 NULL);
298 ASSERT_EQ(OPENED, dir.Open(kIndexTestName, &delegate_,
299 NullTransactionObserver()));
300 {
301 WriteTransaction wtrans(FROM_HERE, UNITTEST, &dir);
302 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), name);
303 ASSERT_TRUE(me.good());
304 me.PutId(id);
305 me.PutBaseVersion(1);
306 me.PutUniqueClientTag(tag);
307 written_metahandle = me.GetMetahandle();
308 }
309 dir.SaveChanges();
310 }
311
312 // The DB was closed. Now reopen it. This will cause index regeneration.
313 {
314 Directory dir(new OnDiskDirectoryBackingStore(kIndexTestName, db_path_),
315 &handler_,
316 NULL,
317 NULL,
318 NULL);
319 ASSERT_EQ(OPENED, dir.Open(kIndexTestName,
320 &delegate_, NullTransactionObserver()));
321
322 ReadTransaction trans(FROM_HERE, &dir);
323 Entry me(&trans, GET_BY_CLIENT_TAG, tag);
324 ASSERT_TRUE(me.good());
325 EXPECT_EQ(me.GetId(), id);
326 EXPECT_EQ(me.GetBaseVersion(), 1);
327 EXPECT_EQ(me.GetUniqueClientTag(), tag);
328 EXPECT_EQ(me.GetMetahandle(), written_metahandle);
329 }
330 }
331
TEST_F(SyncableGeneralTest,ClientIndexRebuildsDeletedProperly)332 TEST_F(SyncableGeneralTest, ClientIndexRebuildsDeletedProperly) {
333 TestIdFactory factory;
334 const Id id = factory.NewServerId();
335 std::string tag = "dietcoke";
336
337 // Test creating a deleted, unsynced, server meta entry.
338 {
339 Directory dir(new OnDiskDirectoryBackingStore(kIndexTestName, db_path_),
340 &handler_,
341 NULL,
342 NULL,
343 NULL);
344 ASSERT_EQ(OPENED, dir.Open(kIndexTestName, &delegate_,
345 NullTransactionObserver()));
346 {
347 WriteTransaction wtrans(FROM_HERE, UNITTEST, &dir);
348 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), "deleted");
349 ASSERT_TRUE(me.good());
350 me.PutId(id);
351 me.PutBaseVersion(1);
352 me.PutUniqueClientTag(tag);
353 me.PutIsDel(true);
354 me.PutIsUnsynced(true); // Or it might be purged.
355 }
356 dir.SaveChanges();
357 }
358
359 // The DB was closed. Now reopen it. This will cause index regeneration.
360 // Should still be present and valid in the client tag index.
361 {
362 Directory dir(new OnDiskDirectoryBackingStore(kIndexTestName, db_path_),
363 &handler_,
364 NULL,
365 NULL,
366 NULL);
367 ASSERT_EQ(OPENED, dir.Open(kIndexTestName, &delegate_,
368 NullTransactionObserver()));
369
370 ReadTransaction trans(FROM_HERE, &dir);
371 Entry me(&trans, GET_BY_CLIENT_TAG, tag);
372 ASSERT_TRUE(me.good());
373 EXPECT_EQ(me.GetId(), id);
374 EXPECT_EQ(me.GetUniqueClientTag(), tag);
375 EXPECT_TRUE(me.GetIsDel());
376 EXPECT_TRUE(me.GetIsUnsynced());
377 }
378 }
379
TEST_F(SyncableGeneralTest,ToValue)380 TEST_F(SyncableGeneralTest, ToValue) {
381 Directory dir(new InMemoryDirectoryBackingStore("SimpleTest"),
382 &handler_,
383 NULL,
384 NULL,
385 NULL);
386 ASSERT_EQ(OPENED, dir.Open(
387 "SimpleTest", &delegate_, NullTransactionObserver()));
388
389 const Id id = TestIdFactory::FromNumber(99);
390 {
391 ReadTransaction rtrans(FROM_HERE, &dir);
392 Entry e(&rtrans, GET_BY_ID, id);
393 EXPECT_FALSE(e.good()); // Hasn't been written yet.
394
395 scoped_ptr<base::DictionaryValue> value(e.ToValue(NULL));
396 ExpectDictBooleanValue(false, *value, "good");
397 EXPECT_EQ(1u, value->size());
398 }
399
400 // Test creating a new meta entry.
401 {
402 WriteTransaction wtrans(FROM_HERE, UNITTEST, &dir);
403 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), "new");
404 ASSERT_TRUE(me.good());
405 me.PutId(id);
406 me.PutBaseVersion(1);
407
408 scoped_ptr<base::DictionaryValue> value(me.ToValue(NULL));
409 ExpectDictBooleanValue(true, *value, "good");
410 EXPECT_TRUE(value->HasKey("kernel"));
411 ExpectDictStringValue("Bookmarks", *value, "modelType");
412 ExpectDictBooleanValue(true, *value, "existsOnClientBecauseNameIsNonEmpty");
413 ExpectDictBooleanValue(false, *value, "isRoot");
414 }
415
416 dir.SaveChanges();
417 }
418
419 // Test that the bookmark tag generation algorithm remains unchanged.
TEST_F(SyncableGeneralTest,BookmarkTagTest)420 TEST_F(SyncableGeneralTest, BookmarkTagTest) {
421 InMemoryDirectoryBackingStore* store = new InMemoryDirectoryBackingStore("x");
422
423 // The two inputs that form the bookmark tag are the directory's cache_guid
424 // and its next_id value. We don't need to take any action to ensure
425 // consistent next_id values, but we do need to explicitly request that our
426 // InMemoryDirectoryBackingStore always return the same cache_guid.
427 store->request_consistent_cache_guid();
428
429 Directory dir(store, &handler_, NULL, NULL, NULL);
430 ASSERT_EQ(OPENED, dir.Open("x", &delegate_, NullTransactionObserver()));
431
432 {
433 WriteTransaction wtrans(FROM_HERE, UNITTEST, &dir);
434 MutableEntry bm(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), "bm");
435 bm.PutIsUnsynced(true);
436
437 // If this assertion fails, that might indicate that the algorithm used to
438 // generate bookmark tags has been modified. This could have implications
439 // for bookmark ordering. Please make sure you know what you're doing if
440 // you intend to make such a change.
441 ASSERT_EQ("6wHRAb3kbnXV5GHrejp4/c1y5tw=", bm.GetUniqueBookmarkTag());
442 }
443 }
444
445 // A test fixture for syncable::Directory. Uses an in-memory database to keep
446 // the unit tests fast.
447 class SyncableDirectoryTest : public testing::Test {
448 protected:
449 base::MessageLoop message_loop_;
450 static const char kName[];
451
SetUp()452 virtual void SetUp() {
453 dir_.reset(new Directory(new InMemoryDirectoryBackingStore(kName),
454 &handler_,
455 NULL,
456 NULL,
457 NULL));
458 ASSERT_TRUE(dir_.get());
459 ASSERT_EQ(OPENED, dir_->Open(kName, &delegate_,
460 NullTransactionObserver()));
461 ASSERT_TRUE(dir_->good());
462 }
463
TearDown()464 virtual void TearDown() {
465 if (dir_)
466 dir_->SaveChanges();
467 dir_.reset();
468 }
469
GetAllMetaHandles(BaseTransaction * trans,MetahandleSet * result)470 void GetAllMetaHandles(BaseTransaction* trans, MetahandleSet* result) {
471 dir_->GetAllMetaHandles(trans, result);
472 }
473
IsInDirtyMetahandles(int64 metahandle)474 bool IsInDirtyMetahandles(int64 metahandle) {
475 return 1 == dir_->kernel_->dirty_metahandles.count(metahandle);
476 }
477
IsInMetahandlesToPurge(int64 metahandle)478 bool IsInMetahandlesToPurge(int64 metahandle) {
479 return 1 == dir_->kernel_->metahandles_to_purge.count(metahandle);
480 }
481
CheckPurgeEntriesWithTypeInSucceeded(ModelTypeSet types_to_purge,bool before_reload)482 void CheckPurgeEntriesWithTypeInSucceeded(ModelTypeSet types_to_purge,
483 bool before_reload) {
484 SCOPED_TRACE(testing::Message("Before reload: ") << before_reload);
485 {
486 ReadTransaction trans(FROM_HERE, dir_.get());
487 MetahandleSet all_set;
488 dir_->GetAllMetaHandles(&trans, &all_set);
489 EXPECT_EQ(4U, all_set.size());
490 if (before_reload)
491 EXPECT_EQ(6U, dir_->kernel_->metahandles_to_purge.size());
492 for (MetahandleSet::iterator iter = all_set.begin();
493 iter != all_set.end(); ++iter) {
494 Entry e(&trans, GET_BY_HANDLE, *iter);
495 const ModelType local_type = e.GetModelType();
496 const ModelType server_type = e.GetServerModelType();
497
498 // Note the dance around incrementing |it|, since we sometimes erase().
499 if ((IsRealDataType(local_type) &&
500 types_to_purge.Has(local_type)) ||
501 (IsRealDataType(server_type) &&
502 types_to_purge.Has(server_type))) {
503 FAIL() << "Illegal type should have been deleted.";
504 }
505 }
506 }
507
508 for (ModelTypeSet::Iterator it = types_to_purge.First();
509 it.Good(); it.Inc()) {
510 EXPECT_FALSE(dir_->InitialSyncEndedForType(it.Get()));
511 }
512 EXPECT_FALSE(types_to_purge.Has(BOOKMARKS));
513 EXPECT_TRUE(dir_->InitialSyncEndedForType(BOOKMARKS));
514 }
515
516 FakeEncryptor encryptor_;
517 TestUnrecoverableErrorHandler handler_;
518 scoped_ptr<Directory> dir_;
519 NullDirectoryChangeDelegate delegate_;
520
521 // Creates an empty entry and sets the ID field to a default one.
CreateEntry(const std::string & entryname)522 void CreateEntry(const std::string& entryname) {
523 CreateEntry(entryname, TestIdFactory::FromNumber(-99));
524 }
525
526 // Creates an empty entry and sets the ID field to id.
CreateEntry(const std::string & entryname,const int id)527 void CreateEntry(const std::string& entryname, const int id) {
528 CreateEntry(entryname, TestIdFactory::FromNumber(id));
529 }
CreateEntry(const std::string & entryname,Id id)530 void CreateEntry(const std::string& entryname, Id id) {
531 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get());
532 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), entryname);
533 ASSERT_TRUE(me.good());
534 me.PutId(id);
535 me.PutIsUnsynced(true);
536 }
537
538 void ValidateEntry(BaseTransaction* trans,
539 int64 id,
540 bool check_name,
541 const std::string& name,
542 int64 base_version,
543 int64 server_version,
544 bool is_del);
545
546 // When a directory is saved then loaded from disk, it will pass through
547 // DropDeletedEntries(). This will remove some entries from the directory.
548 // This function is intended to simulate that process.
549 //
550 // WARNING: The directory will be deleted by this operation. You should
551 // not have any pointers to the directory (open transactions included)
552 // when you call this.
553 DirOpenResult SimulateSaveAndReloadDir();
554
555 // This function will close and re-open the directory without saving any
556 // pending changes. This is intended to simulate the recovery from a crash
557 // scenario. The same warnings for SimulateSaveAndReloadDir apply here.
558 DirOpenResult SimulateCrashAndReloadDir();
559
560 private:
561 // A helper function for Simulate{Save,Crash}AndReloadDir.
562 DirOpenResult ReloadDirImpl();
563 };
564
TEST_F(SyncableDirectoryTest,TakeSnapshotGetsMetahandlesToPurge)565 TEST_F(SyncableDirectoryTest, TakeSnapshotGetsMetahandlesToPurge) {
566 const int metas_to_create = 50;
567 MetahandleSet expected_purges;
568 MetahandleSet all_handles;
569 {
570 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
571 for (int i = 0; i < metas_to_create; i++) {
572 MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo");
573 e.PutIsUnsynced(true);
574 sync_pb::EntitySpecifics specs;
575 if (i % 2 == 0) {
576 AddDefaultFieldValue(BOOKMARKS, &specs);
577 expected_purges.insert(e.GetMetahandle());
578 all_handles.insert(e.GetMetahandle());
579 } else {
580 AddDefaultFieldValue(PREFERENCES, &specs);
581 all_handles.insert(e.GetMetahandle());
582 }
583 e.PutSpecifics(specs);
584 e.PutServerSpecifics(specs);
585 }
586 }
587
588 ModelTypeSet to_purge(BOOKMARKS);
589 dir_->PurgeEntriesWithTypeIn(to_purge, ModelTypeSet(), ModelTypeSet());
590
591 Directory::SaveChangesSnapshot snapshot1;
592 base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex);
593 dir_->TakeSnapshotForSaveChanges(&snapshot1);
594 EXPECT_TRUE(expected_purges == snapshot1.metahandles_to_purge);
595
596 to_purge.Clear();
597 to_purge.Put(PREFERENCES);
598 dir_->PurgeEntriesWithTypeIn(to_purge, ModelTypeSet(), ModelTypeSet());
599
600 dir_->HandleSaveChangesFailure(snapshot1);
601
602 Directory::SaveChangesSnapshot snapshot2;
603 dir_->TakeSnapshotForSaveChanges(&snapshot2);
604 EXPECT_TRUE(all_handles == snapshot2.metahandles_to_purge);
605 }
606
TEST_F(SyncableDirectoryTest,TakeSnapshotGetsAllDirtyHandlesTest)607 TEST_F(SyncableDirectoryTest, TakeSnapshotGetsAllDirtyHandlesTest) {
608 const int metahandles_to_create = 100;
609 std::vector<int64> expected_dirty_metahandles;
610 {
611 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
612 for (int i = 0; i < metahandles_to_create; i++) {
613 MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo");
614 expected_dirty_metahandles.push_back(e.GetMetahandle());
615 e.PutIsUnsynced(true);
616 }
617 }
618 // Fake SaveChanges() and make sure we got what we expected.
619 {
620 Directory::SaveChangesSnapshot snapshot;
621 base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex);
622 dir_->TakeSnapshotForSaveChanges(&snapshot);
623 // Make sure there's an entry for each new metahandle. Make sure all
624 // entries are marked dirty.
625 ASSERT_EQ(expected_dirty_metahandles.size(), snapshot.dirty_metas.size());
626 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
627 i != snapshot.dirty_metas.end(); ++i) {
628 ASSERT_TRUE((*i)->is_dirty());
629 }
630 dir_->VacuumAfterSaveChanges(snapshot);
631 }
632 // Put a new value with existing transactions as well as adding new ones.
633 {
634 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
635 std::vector<int64> new_dirty_metahandles;
636 for (std::vector<int64>::const_iterator i =
637 expected_dirty_metahandles.begin();
638 i != expected_dirty_metahandles.end(); ++i) {
639 // Change existing entries to directories to dirty them.
640 MutableEntry e1(&trans, GET_BY_HANDLE, *i);
641 e1.PutIsDir(true);
642 e1.PutIsUnsynced(true);
643 // Add new entries
644 MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), "bar");
645 e2.PutIsUnsynced(true);
646 new_dirty_metahandles.push_back(e2.GetMetahandle());
647 }
648 expected_dirty_metahandles.insert(expected_dirty_metahandles.end(),
649 new_dirty_metahandles.begin(), new_dirty_metahandles.end());
650 }
651 // Fake SaveChanges() and make sure we got what we expected.
652 {
653 Directory::SaveChangesSnapshot snapshot;
654 base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex);
655 dir_->TakeSnapshotForSaveChanges(&snapshot);
656 // Make sure there's an entry for each new metahandle. Make sure all
657 // entries are marked dirty.
658 EXPECT_EQ(expected_dirty_metahandles.size(), snapshot.dirty_metas.size());
659 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
660 i != snapshot.dirty_metas.end(); ++i) {
661 EXPECT_TRUE((*i)->is_dirty());
662 }
663 dir_->VacuumAfterSaveChanges(snapshot);
664 }
665 }
666
TEST_F(SyncableDirectoryTest,TakeSnapshotGetsOnlyDirtyHandlesTest)667 TEST_F(SyncableDirectoryTest, TakeSnapshotGetsOnlyDirtyHandlesTest) {
668 const int metahandles_to_create = 100;
669
670 // half of 2 * metahandles_to_create
671 const unsigned int number_changed = 100u;
672 std::vector<int64> expected_dirty_metahandles;
673 {
674 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
675 for (int i = 0; i < metahandles_to_create; i++) {
676 MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo");
677 expected_dirty_metahandles.push_back(e.GetMetahandle());
678 e.PutIsUnsynced(true);
679 }
680 }
681 dir_->SaveChanges();
682 // Put a new value with existing transactions as well as adding new ones.
683 {
684 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
685 std::vector<int64> new_dirty_metahandles;
686 for (std::vector<int64>::const_iterator i =
687 expected_dirty_metahandles.begin();
688 i != expected_dirty_metahandles.end(); ++i) {
689 // Change existing entries to directories to dirty them.
690 MutableEntry e1(&trans, GET_BY_HANDLE, *i);
691 ASSERT_TRUE(e1.good());
692 e1.PutIsDir(true);
693 e1.PutIsUnsynced(true);
694 // Add new entries
695 MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), "bar");
696 e2.PutIsUnsynced(true);
697 new_dirty_metahandles.push_back(e2.GetMetahandle());
698 }
699 expected_dirty_metahandles.insert(expected_dirty_metahandles.end(),
700 new_dirty_metahandles.begin(), new_dirty_metahandles.end());
701 }
702 dir_->SaveChanges();
703 // Don't make any changes whatsoever and ensure nothing comes back.
704 {
705 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
706 for (std::vector<int64>::const_iterator i =
707 expected_dirty_metahandles.begin();
708 i != expected_dirty_metahandles.end(); ++i) {
709 MutableEntry e(&trans, GET_BY_HANDLE, *i);
710 ASSERT_TRUE(e.good());
711 // We aren't doing anything to dirty these entries.
712 }
713 }
714 // Fake SaveChanges() and make sure we got what we expected.
715 {
716 Directory::SaveChangesSnapshot snapshot;
717 base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex);
718 dir_->TakeSnapshotForSaveChanges(&snapshot);
719 // Make sure there are no dirty_metahandles.
720 EXPECT_EQ(0u, snapshot.dirty_metas.size());
721 dir_->VacuumAfterSaveChanges(snapshot);
722 }
723 {
724 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
725 bool should_change = false;
726 for (std::vector<int64>::const_iterator i =
727 expected_dirty_metahandles.begin();
728 i != expected_dirty_metahandles.end(); ++i) {
729 // Maybe change entries by flipping IS_DIR.
730 MutableEntry e(&trans, GET_BY_HANDLE, *i);
731 ASSERT_TRUE(e.good());
732 should_change = !should_change;
733 if (should_change) {
734 bool not_dir = !e.GetIsDir();
735 e.PutIsDir(not_dir);
736 e.PutIsUnsynced(true);
737 }
738 }
739 }
740 // Fake SaveChanges() and make sure we got what we expected.
741 {
742 Directory::SaveChangesSnapshot snapshot;
743 base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex);
744 dir_->TakeSnapshotForSaveChanges(&snapshot);
745 // Make sure there's an entry for each changed metahandle. Make sure all
746 // entries are marked dirty.
747 EXPECT_EQ(number_changed, snapshot.dirty_metas.size());
748 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
749 i != snapshot.dirty_metas.end(); ++i) {
750 EXPECT_TRUE((*i)->is_dirty());
751 }
752 dir_->VacuumAfterSaveChanges(snapshot);
753 }
754 }
755
756 // Test delete journals management.
TEST_F(SyncableDirectoryTest,ManageDeleteJournals)757 TEST_F(SyncableDirectoryTest, ManageDeleteJournals) {
758 sync_pb::EntitySpecifics bookmark_specifics;
759 AddDefaultFieldValue(BOOKMARKS, &bookmark_specifics);
760 bookmark_specifics.mutable_bookmark()->set_url("url");
761
762 Id id1 = TestIdFactory::FromNumber(-1);
763 Id id2 = TestIdFactory::FromNumber(-2);
764 int64 handle1 = 0;
765 int64 handle2 = 0;
766 {
767 // Create two bookmark entries and save in database.
768 CreateEntry("item1", id1);
769 CreateEntry("item2", id2);
770 {
771 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
772 MutableEntry item1(&trans, GET_BY_ID, id1);
773 ASSERT_TRUE(item1.good());
774 handle1 = item1.GetMetahandle();
775 item1.PutSpecifics(bookmark_specifics);
776 item1.PutServerSpecifics(bookmark_specifics);
777 MutableEntry item2(&trans, GET_BY_ID, id2);
778 ASSERT_TRUE(item2.good());
779 handle2 = item2.GetMetahandle();
780 item2.PutSpecifics(bookmark_specifics);
781 item2.PutServerSpecifics(bookmark_specifics);
782 }
783 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir());
784 }
785
786 { // Test adding and saving delete journals.
787 DeleteJournal* delete_journal = dir_->delete_journal();
788 {
789 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
790 EntryKernelSet journal_entries;
791 delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries);
792 ASSERT_EQ(0u, journal_entries.size());
793
794 // Set SERVER_IS_DEL of the entries to true and they should be added to
795 // delete journals.
796 MutableEntry item1(&trans, GET_BY_ID, id1);
797 ASSERT_TRUE(item1.good());
798 item1.PutServerIsDel(true);
799 MutableEntry item2(&trans, GET_BY_ID, id2);
800 ASSERT_TRUE(item2.good());
801 item2.PutServerIsDel(true);
802 EntryKernel tmp;
803 tmp.put(ID, id1);
804 EXPECT_TRUE(delete_journal->delete_journals_.count(&tmp));
805 tmp.put(ID, id2);
806 EXPECT_TRUE(delete_journal->delete_journals_.count(&tmp));
807 }
808
809 // Save delete journals in database and verify memory clearing.
810 ASSERT_TRUE(dir_->SaveChanges());
811 {
812 ReadTransaction trans(FROM_HERE, dir_.get());
813 EXPECT_EQ(0u, delete_journal->GetDeleteJournalSize(&trans));
814 }
815 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir());
816 }
817
818 {
819 {
820 // Test reading delete journals from database.
821 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
822 DeleteJournal* delete_journal = dir_->delete_journal();
823 EntryKernelSet journal_entries;
824 delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries);
825 ASSERT_EQ(2u, journal_entries.size());
826 EntryKernel tmp;
827 tmp.put(META_HANDLE, handle1);
828 EXPECT_TRUE(journal_entries.count(&tmp));
829 tmp.put(META_HANDLE, handle2);
830 EXPECT_TRUE(journal_entries.count(&tmp));
831
832 // Purge item2.
833 MetahandleSet to_purge;
834 to_purge.insert(handle2);
835 delete_journal->PurgeDeleteJournals(&trans, to_purge);
836
837 // Verify that item2 is purged from journals in memory and will be
838 // purged from database.
839 tmp.put(ID, id2);
840 EXPECT_FALSE(delete_journal->delete_journals_.count(&tmp));
841 EXPECT_EQ(1u, delete_journal->delete_journals_to_purge_.size());
842 EXPECT_TRUE(delete_journal->delete_journals_to_purge_.count(handle2));
843 }
844 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir());
845 }
846
847 {
848 {
849 // Verify purged entry is gone in database.
850 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
851 DeleteJournal* delete_journal = dir_->delete_journal();
852 EntryKernelSet journal_entries;
853 delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries);
854 ASSERT_EQ(1u, journal_entries.size());
855 EntryKernel tmp;
856 tmp.put(ID, id1);
857 tmp.put(META_HANDLE, handle1);
858 EXPECT_TRUE(journal_entries.count(&tmp));
859
860 // Undelete item1.
861 MutableEntry item1(&trans, GET_BY_ID, id1);
862 ASSERT_TRUE(item1.good());
863 item1.PutServerIsDel(false);
864 EXPECT_TRUE(delete_journal->delete_journals_.empty());
865 EXPECT_EQ(1u, delete_journal->delete_journals_to_purge_.size());
866 EXPECT_TRUE(delete_journal->delete_journals_to_purge_.count(handle1));
867 }
868 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir());
869 }
870
871 {
872 // Verify undeleted entry is gone from database.
873 ReadTransaction trans(FROM_HERE, dir_.get());
874 DeleteJournal* delete_journal = dir_->delete_journal();
875 ASSERT_EQ(0u, delete_journal->GetDeleteJournalSize(&trans));
876 }
877 }
878
879 const char SyncableDirectoryTest::kName[] = "Foo";
880
881 namespace {
882
TEST_F(SyncableDirectoryTest,TestBasicLookupNonExistantID)883 TEST_F(SyncableDirectoryTest, TestBasicLookupNonExistantID) {
884 ReadTransaction rtrans(FROM_HERE, dir_.get());
885 Entry e(&rtrans, GET_BY_ID, TestIdFactory::FromNumber(-99));
886 ASSERT_FALSE(e.good());
887 }
888
TEST_F(SyncableDirectoryTest,TestBasicLookupValidID)889 TEST_F(SyncableDirectoryTest, TestBasicLookupValidID) {
890 CreateEntry("rtc");
891 ReadTransaction rtrans(FROM_HERE, dir_.get());
892 Entry e(&rtrans, GET_BY_ID, TestIdFactory::FromNumber(-99));
893 ASSERT_TRUE(e.good());
894 }
895
TEST_F(SyncableDirectoryTest,TestDelete)896 TEST_F(SyncableDirectoryTest, TestDelete) {
897 std::string name = "peanut butter jelly time";
898 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
899 MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), name);
900 ASSERT_TRUE(e1.good());
901 e1.PutIsDel(true);
902 MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), name);
903 ASSERT_TRUE(e2.good());
904 e2.PutIsDel(true);
905 MutableEntry e3(&trans, CREATE, BOOKMARKS, trans.root_id(), name);
906 ASSERT_TRUE(e3.good());
907 e3.PutIsDel(true);
908
909 e1.PutIsDel(false);
910 e2.PutIsDel(false);
911 e3.PutIsDel(false);
912
913 e1.PutIsDel(true);
914 e2.PutIsDel(true);
915 e3.PutIsDel(true);
916 }
917
TEST_F(SyncableDirectoryTest,TestGetUnsynced)918 TEST_F(SyncableDirectoryTest, TestGetUnsynced) {
919 Directory::Metahandles handles;
920 int64 handle1, handle2;
921 {
922 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
923
924 dir_->GetUnsyncedMetaHandles(&trans, &handles);
925 ASSERT_TRUE(0 == handles.size());
926
927 MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "abba");
928 ASSERT_TRUE(e1.good());
929 handle1 = e1.GetMetahandle();
930 e1.PutBaseVersion(1);
931 e1.PutIsDir(true);
932 e1.PutId(TestIdFactory::FromNumber(101));
933
934 MutableEntry e2(&trans, CREATE, BOOKMARKS, e1.GetId(), "bread");
935 ASSERT_TRUE(e2.good());
936 handle2 = e2.GetMetahandle();
937 e2.PutBaseVersion(1);
938 e2.PutId(TestIdFactory::FromNumber(102));
939 }
940 dir_->SaveChanges();
941 {
942 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
943
944 dir_->GetUnsyncedMetaHandles(&trans, &handles);
945 ASSERT_TRUE(0 == handles.size());
946
947 MutableEntry e3(&trans, GET_BY_HANDLE, handle1);
948 ASSERT_TRUE(e3.good());
949 e3.PutIsUnsynced(true);
950 }
951 dir_->SaveChanges();
952 {
953 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
954 dir_->GetUnsyncedMetaHandles(&trans, &handles);
955 ASSERT_TRUE(1 == handles.size());
956 ASSERT_TRUE(handle1 == handles[0]);
957
958 MutableEntry e4(&trans, GET_BY_HANDLE, handle2);
959 ASSERT_TRUE(e4.good());
960 e4.PutIsUnsynced(true);
961 }
962 dir_->SaveChanges();
963 {
964 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
965 dir_->GetUnsyncedMetaHandles(&trans, &handles);
966 ASSERT_TRUE(2 == handles.size());
967 if (handle1 == handles[0]) {
968 ASSERT_TRUE(handle2 == handles[1]);
969 } else {
970 ASSERT_TRUE(handle2 == handles[0]);
971 ASSERT_TRUE(handle1 == handles[1]);
972 }
973
974 MutableEntry e5(&trans, GET_BY_HANDLE, handle1);
975 ASSERT_TRUE(e5.good());
976 ASSERT_TRUE(e5.GetIsUnsynced());
977 ASSERT_TRUE(e5.PutIsUnsynced(false));
978 ASSERT_FALSE(e5.GetIsUnsynced());
979 }
980 dir_->SaveChanges();
981 {
982 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
983 dir_->GetUnsyncedMetaHandles(&trans, &handles);
984 ASSERT_TRUE(1 == handles.size());
985 ASSERT_TRUE(handle2 == handles[0]);
986 }
987 }
988
TEST_F(SyncableDirectoryTest,TestGetUnappliedUpdates)989 TEST_F(SyncableDirectoryTest, TestGetUnappliedUpdates) {
990 std::vector<int64> handles;
991 int64 handle1, handle2;
992 const FullModelTypeSet all_types = FullModelTypeSet::All();
993 {
994 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
995
996 dir_->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles);
997 ASSERT_TRUE(0 == handles.size());
998
999 MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "abba");
1000 ASSERT_TRUE(e1.good());
1001 handle1 = e1.GetMetahandle();
1002 e1.PutIsUnappliedUpdate(false);
1003 e1.PutBaseVersion(1);
1004 e1.PutId(TestIdFactory::FromNumber(101));
1005 e1.PutIsDir(true);
1006
1007 MutableEntry e2(&trans, CREATE, BOOKMARKS, e1.GetId(), "bread");
1008 ASSERT_TRUE(e2.good());
1009 handle2 = e2.GetMetahandle();
1010 e2.PutIsUnappliedUpdate(false);
1011 e2.PutBaseVersion(1);
1012 e2.PutId(TestIdFactory::FromNumber(102));
1013 }
1014 dir_->SaveChanges();
1015 {
1016 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1017
1018 dir_->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles);
1019 ASSERT_TRUE(0 == handles.size());
1020
1021 MutableEntry e3(&trans, GET_BY_HANDLE, handle1);
1022 ASSERT_TRUE(e3.good());
1023 e3.PutIsUnappliedUpdate(true);
1024 }
1025 dir_->SaveChanges();
1026 {
1027 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1028 dir_->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles);
1029 ASSERT_TRUE(1 == handles.size());
1030 ASSERT_TRUE(handle1 == handles[0]);
1031
1032 MutableEntry e4(&trans, GET_BY_HANDLE, handle2);
1033 ASSERT_TRUE(e4.good());
1034 e4.PutIsUnappliedUpdate(true);
1035 }
1036 dir_->SaveChanges();
1037 {
1038 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1039 dir_->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles);
1040 ASSERT_TRUE(2 == handles.size());
1041 if (handle1 == handles[0]) {
1042 ASSERT_TRUE(handle2 == handles[1]);
1043 } else {
1044 ASSERT_TRUE(handle2 == handles[0]);
1045 ASSERT_TRUE(handle1 == handles[1]);
1046 }
1047
1048 MutableEntry e5(&trans, GET_BY_HANDLE, handle1);
1049 ASSERT_TRUE(e5.good());
1050 e5.PutIsUnappliedUpdate(false);
1051 }
1052 dir_->SaveChanges();
1053 {
1054 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1055 dir_->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles);
1056 ASSERT_TRUE(1 == handles.size());
1057 ASSERT_TRUE(handle2 == handles[0]);
1058 }
1059 }
1060
1061
TEST_F(SyncableDirectoryTest,DeleteBug_531383)1062 TEST_F(SyncableDirectoryTest, DeleteBug_531383) {
1063 // Try to evoke a check failure...
1064 TestIdFactory id_factory;
1065 int64 grandchild_handle;
1066 {
1067 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get());
1068 MutableEntry parent(&wtrans, CREATE, BOOKMARKS, id_factory.root(), "Bob");
1069 ASSERT_TRUE(parent.good());
1070 parent.PutIsDir(true);
1071 parent.PutId(id_factory.NewServerId());
1072 parent.PutBaseVersion(1);
1073 MutableEntry child(&wtrans, CREATE, BOOKMARKS, parent.GetId(), "Bob");
1074 ASSERT_TRUE(child.good());
1075 child.PutIsDir(true);
1076 child.PutId(id_factory.NewServerId());
1077 child.PutBaseVersion(1);
1078 MutableEntry grandchild(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob");
1079 ASSERT_TRUE(grandchild.good());
1080 grandchild.PutId(id_factory.NewServerId());
1081 grandchild.PutBaseVersion(1);
1082 grandchild.PutIsDel(true);
1083 MutableEntry twin(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob");
1084 ASSERT_TRUE(twin.good());
1085 twin.PutIsDel(true);
1086 grandchild.PutIsDel(false);
1087
1088 grandchild_handle = grandchild.GetMetahandle();
1089 }
1090 dir_->SaveChanges();
1091 {
1092 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get());
1093 MutableEntry grandchild(&wtrans, GET_BY_HANDLE, grandchild_handle);
1094 grandchild.PutIsDel(true); // Used to CHECK fail here.
1095 }
1096 }
1097
IsLegalNewParent(const Entry & a,const Entry & b)1098 static inline bool IsLegalNewParent(const Entry& a, const Entry& b) {
1099 return IsLegalNewParent(a.trans(), a.GetId(), b.GetId());
1100 }
1101
TEST_F(SyncableDirectoryTest,TestIsLegalNewParent)1102 TEST_F(SyncableDirectoryTest, TestIsLegalNewParent) {
1103 TestIdFactory id_factory;
1104 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get());
1105 Entry root(&wtrans, GET_BY_ID, id_factory.root());
1106 ASSERT_TRUE(root.good());
1107 MutableEntry parent(&wtrans, CREATE, BOOKMARKS, root.GetId(), "Bob");
1108 ASSERT_TRUE(parent.good());
1109 parent.PutIsDir(true);
1110 parent.PutId(id_factory.NewServerId());
1111 parent.PutBaseVersion(1);
1112 MutableEntry child(&wtrans, CREATE, BOOKMARKS, parent.GetId(), "Bob");
1113 ASSERT_TRUE(child.good());
1114 child.PutIsDir(true);
1115 child.PutId(id_factory.NewServerId());
1116 child.PutBaseVersion(1);
1117 MutableEntry grandchild(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob");
1118 ASSERT_TRUE(grandchild.good());
1119 grandchild.PutId(id_factory.NewServerId());
1120 grandchild.PutBaseVersion(1);
1121
1122 MutableEntry parent2(&wtrans, CREATE, BOOKMARKS, root.GetId(), "Pete");
1123 ASSERT_TRUE(parent2.good());
1124 parent2.PutIsDir(true);
1125 parent2.PutId(id_factory.NewServerId());
1126 parent2.PutBaseVersion(1);
1127 MutableEntry child2(&wtrans, CREATE, BOOKMARKS, parent2.GetId(), "Pete");
1128 ASSERT_TRUE(child2.good());
1129 child2.PutIsDir(true);
1130 child2.PutId(id_factory.NewServerId());
1131 child2.PutBaseVersion(1);
1132 MutableEntry grandchild2(&wtrans, CREATE, BOOKMARKS, child2.GetId(), "Pete");
1133 ASSERT_TRUE(grandchild2.good());
1134 grandchild2.PutId(id_factory.NewServerId());
1135 grandchild2.PutBaseVersion(1);
1136 // resulting tree
1137 // root
1138 // / |
1139 // parent parent2
1140 // | |
1141 // child child2
1142 // | |
1143 // grandchild grandchild2
1144 ASSERT_TRUE(IsLegalNewParent(child, root));
1145 ASSERT_TRUE(IsLegalNewParent(child, parent));
1146 ASSERT_FALSE(IsLegalNewParent(child, child));
1147 ASSERT_FALSE(IsLegalNewParent(child, grandchild));
1148 ASSERT_TRUE(IsLegalNewParent(child, parent2));
1149 ASSERT_TRUE(IsLegalNewParent(child, grandchild2));
1150 ASSERT_FALSE(IsLegalNewParent(parent, grandchild));
1151 ASSERT_FALSE(IsLegalNewParent(root, grandchild));
1152 ASSERT_FALSE(IsLegalNewParent(parent, grandchild));
1153 }
1154
TEST_F(SyncableDirectoryTest,TestEntryIsInFolder)1155 TEST_F(SyncableDirectoryTest, TestEntryIsInFolder) {
1156 // Create a subdir and an entry.
1157 int64 entry_handle;
1158 syncable::Id folder_id;
1159 syncable::Id entry_id;
1160 std::string entry_name = "entry";
1161
1162 {
1163 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1164 MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "folder");
1165 ASSERT_TRUE(folder.good());
1166 folder.PutIsDir(true);
1167 EXPECT_TRUE(folder.PutIsUnsynced(true));
1168 folder_id = folder.GetId();
1169
1170 MutableEntry entry(&trans, CREATE, BOOKMARKS, folder.GetId(), entry_name);
1171 ASSERT_TRUE(entry.good());
1172 entry_handle = entry.GetMetahandle();
1173 entry.PutIsUnsynced(true);
1174 entry_id = entry.GetId();
1175 }
1176
1177 // Make sure we can find the entry in the folder.
1178 {
1179 ReadTransaction trans(FROM_HERE, dir_.get());
1180 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), entry_name));
1181 EXPECT_EQ(1, CountEntriesWithName(&trans, folder_id, entry_name));
1182
1183 Entry entry(&trans, GET_BY_ID, entry_id);
1184 ASSERT_TRUE(entry.good());
1185 EXPECT_EQ(entry_handle, entry.GetMetahandle());
1186 EXPECT_TRUE(entry.GetNonUniqueName()== entry_name);
1187 EXPECT_TRUE(entry.GetParentId()== folder_id);
1188 }
1189 }
1190
TEST_F(SyncableDirectoryTest,TestParentIdIndexUpdate)1191 TEST_F(SyncableDirectoryTest, TestParentIdIndexUpdate) {
1192 std::string child_name = "child";
1193
1194 WriteTransaction wt(FROM_HERE, UNITTEST, dir_.get());
1195 MutableEntry parent_folder(&wt, CREATE, BOOKMARKS, wt.root_id(), "folder1");
1196 parent_folder.PutIsUnsynced(true);
1197 parent_folder.PutIsDir(true);
1198
1199 MutableEntry parent_folder2(&wt, CREATE, BOOKMARKS, wt.root_id(), "folder2");
1200 parent_folder2.PutIsUnsynced(true);
1201 parent_folder2.PutIsDir(true);
1202
1203 MutableEntry child(&wt, CREATE, BOOKMARKS, parent_folder.GetId(), child_name);
1204 child.PutIsDir(true);
1205 child.PutIsUnsynced(true);
1206
1207 ASSERT_TRUE(child.good());
1208
1209 EXPECT_EQ(0, CountEntriesWithName(&wt, wt.root_id(), child_name));
1210 EXPECT_EQ(parent_folder.GetId(), child.GetParentId());
1211 EXPECT_EQ(1, CountEntriesWithName(&wt, parent_folder.GetId(), child_name));
1212 EXPECT_EQ(0, CountEntriesWithName(&wt, parent_folder2.GetId(), child_name));
1213 child.PutParentId(parent_folder2.GetId());
1214 EXPECT_EQ(parent_folder2.GetId(), child.GetParentId());
1215 EXPECT_EQ(0, CountEntriesWithName(&wt, parent_folder.GetId(), child_name));
1216 EXPECT_EQ(1, CountEntriesWithName(&wt, parent_folder2.GetId(), child_name));
1217 }
1218
TEST_F(SyncableDirectoryTest,TestNoReindexDeletedItems)1219 TEST_F(SyncableDirectoryTest, TestNoReindexDeletedItems) {
1220 std::string folder_name = "folder";
1221 std::string new_name = "new_name";
1222
1223 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1224 MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), folder_name);
1225 ASSERT_TRUE(folder.good());
1226 folder.PutIsDir(true);
1227 folder.PutIsDel(true);
1228
1229 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), folder_name));
1230
1231 MutableEntry deleted(&trans, GET_BY_ID, folder.GetId());
1232 ASSERT_TRUE(deleted.good());
1233 deleted.PutParentId(trans.root_id());
1234 deleted.PutNonUniqueName(new_name);
1235
1236 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), folder_name));
1237 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), new_name));
1238 }
1239
TEST_F(SyncableDirectoryTest,TestCaseChangeRename)1240 TEST_F(SyncableDirectoryTest, TestCaseChangeRename) {
1241 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1242 MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "CaseChange");
1243 ASSERT_TRUE(folder.good());
1244 folder.PutParentId(trans.root_id());
1245 folder.PutNonUniqueName("CASECHANGE");
1246 folder.PutIsDel(true);
1247 }
1248
1249 // Create items of each model type, and check that GetModelType and
1250 // GetServerModelType return the right value.
TEST_F(SyncableDirectoryTest,GetModelType)1251 TEST_F(SyncableDirectoryTest, GetModelType) {
1252 TestIdFactory id_factory;
1253 ModelTypeSet protocol_types = ProtocolTypes();
1254 for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good();
1255 iter.Inc()) {
1256 ModelType datatype = iter.Get();
1257 SCOPED_TRACE(testing::Message("Testing model type ") << datatype);
1258 switch (datatype) {
1259 case UNSPECIFIED:
1260 case TOP_LEVEL_FOLDER:
1261 continue; // Datatype isn't a function of Specifics.
1262 default:
1263 break;
1264 }
1265 sync_pb::EntitySpecifics specifics;
1266 AddDefaultFieldValue(datatype, &specifics);
1267
1268 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1269
1270 MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "Folder");
1271 ASSERT_TRUE(folder.good());
1272 folder.PutId(id_factory.NewServerId());
1273 folder.PutSpecifics(specifics);
1274 folder.PutBaseVersion(1);
1275 folder.PutIsDir(true);
1276 folder.PutIsDel(false);
1277 ASSERT_EQ(datatype, folder.GetModelType());
1278
1279 MutableEntry item(&trans, CREATE, BOOKMARKS, trans.root_id(), "Item");
1280 ASSERT_TRUE(item.good());
1281 item.PutId(id_factory.NewServerId());
1282 item.PutSpecifics(specifics);
1283 item.PutBaseVersion(1);
1284 item.PutIsDir(false);
1285 item.PutIsDel(false);
1286 ASSERT_EQ(datatype, item.GetModelType());
1287
1288 // It's critical that deletion records retain their datatype, so that
1289 // they can be dispatched to the appropriate change processor.
1290 MutableEntry deleted_item(
1291 &trans, CREATE, BOOKMARKS, trans.root_id(), "Deleted Item");
1292 ASSERT_TRUE(item.good());
1293 deleted_item.PutId(id_factory.NewServerId());
1294 deleted_item.PutSpecifics(specifics);
1295 deleted_item.PutBaseVersion(1);
1296 deleted_item.PutIsDir(false);
1297 deleted_item.PutIsDel(true);
1298 ASSERT_EQ(datatype, deleted_item.GetModelType());
1299
1300 MutableEntry server_folder(&trans, CREATE_NEW_UPDATE_ITEM,
1301 id_factory.NewServerId());
1302 ASSERT_TRUE(server_folder.good());
1303 server_folder.PutServerSpecifics(specifics);
1304 server_folder.PutBaseVersion(1);
1305 server_folder.PutServerIsDir(true);
1306 server_folder.PutServerIsDel(false);
1307 ASSERT_EQ(datatype, server_folder.GetServerModelType());
1308
1309 MutableEntry server_item(&trans, CREATE_NEW_UPDATE_ITEM,
1310 id_factory.NewServerId());
1311 ASSERT_TRUE(server_item.good());
1312 server_item.PutServerSpecifics(specifics);
1313 server_item.PutBaseVersion(1);
1314 server_item.PutServerIsDir(false);
1315 server_item.PutServerIsDel(false);
1316 ASSERT_EQ(datatype, server_item.GetServerModelType());
1317
1318 sync_pb::SyncEntity folder_entity;
1319 folder_entity.set_id_string(SyncableIdToProto(id_factory.NewServerId()));
1320 folder_entity.set_deleted(false);
1321 folder_entity.set_folder(true);
1322 folder_entity.mutable_specifics()->CopyFrom(specifics);
1323 ASSERT_EQ(datatype, GetModelType(folder_entity));
1324
1325 sync_pb::SyncEntity item_entity;
1326 item_entity.set_id_string(SyncableIdToProto(id_factory.NewServerId()));
1327 item_entity.set_deleted(false);
1328 item_entity.set_folder(false);
1329 item_entity.mutable_specifics()->CopyFrom(specifics);
1330 ASSERT_EQ(datatype, GetModelType(item_entity));
1331 }
1332 }
1333
1334 // A test that roughly mimics the directory interaction that occurs when a
1335 // bookmark folder and entry are created then synced for the first time. It is
1336 // a more common variant of the 'DeletedAndUnsyncedChild' scenario tested below.
TEST_F(SyncableDirectoryTest,ChangeEntryIDAndUpdateChildren_ParentAndChild)1337 TEST_F(SyncableDirectoryTest, ChangeEntryIDAndUpdateChildren_ParentAndChild) {
1338 TestIdFactory id_factory;
1339 Id orig_parent_id;
1340 Id orig_child_id;
1341
1342 {
1343 // Create two client-side items, a parent and child.
1344 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1345
1346 MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent");
1347 parent.PutIsDir(true);
1348 parent.PutIsUnsynced(true);
1349
1350 MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child");
1351 child.PutIsUnsynced(true);
1352
1353 orig_parent_id = parent.GetId();
1354 orig_child_id = child.GetId();
1355 }
1356
1357 {
1358 // Simulate what happens after committing two items. Their IDs will be
1359 // replaced with server IDs. The child is renamed first, then the parent.
1360 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1361
1362 MutableEntry parent(&trans, GET_BY_ID, orig_parent_id);
1363 MutableEntry child(&trans, GET_BY_ID, orig_child_id);
1364
1365 ChangeEntryIDAndUpdateChildren(&trans, &child, id_factory.NewServerId());
1366 child.PutIsUnsynced(false);
1367 child.PutBaseVersion(1);
1368 child.PutServerVersion(1);
1369
1370 ChangeEntryIDAndUpdateChildren(&trans, &parent, id_factory.NewServerId());
1371 parent.PutIsUnsynced(false);
1372 parent.PutBaseVersion(1);
1373 parent.PutServerVersion(1);
1374 }
1375
1376 // Final check for validity.
1377 EXPECT_EQ(OPENED, SimulateSaveAndReloadDir());
1378 }
1379
1380 // A test based on the scenario where we create a bookmark folder and entry
1381 // locally, but with a twist. In this case, the bookmark is deleted before we
1382 // are able to sync either it or its parent folder. This scenario used to cause
1383 // directory corruption, see crbug.com/125381.
TEST_F(SyncableDirectoryTest,ChangeEntryIDAndUpdateChildren_DeletedAndUnsyncedChild)1384 TEST_F(SyncableDirectoryTest,
1385 ChangeEntryIDAndUpdateChildren_DeletedAndUnsyncedChild) {
1386 TestIdFactory id_factory;
1387 Id orig_parent_id;
1388 Id orig_child_id;
1389
1390 {
1391 // Create two client-side items, a parent and child.
1392 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1393
1394 MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent");
1395 parent.PutIsDir(true);
1396 parent.PutIsUnsynced(true);
1397
1398 MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child");
1399 child.PutIsUnsynced(true);
1400
1401 orig_parent_id = parent.GetId();
1402 orig_child_id = child.GetId();
1403 }
1404
1405 {
1406 // Delete the child.
1407 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1408
1409 MutableEntry child(&trans, GET_BY_ID, orig_child_id);
1410 child.PutIsDel(true);
1411 }
1412
1413 {
1414 // Simulate what happens after committing the parent. Its ID will be
1415 // replaced with server a ID.
1416 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1417
1418 MutableEntry parent(&trans, GET_BY_ID, orig_parent_id);
1419
1420 ChangeEntryIDAndUpdateChildren(&trans, &parent, id_factory.NewServerId());
1421 parent.PutIsUnsynced(false);
1422 parent.PutBaseVersion(1);
1423 parent.PutServerVersion(1);
1424 }
1425
1426 // Final check for validity.
1427 EXPECT_EQ(OPENED, SimulateSaveAndReloadDir());
1428 }
1429
1430 // Ask the directory to generate a unique ID. Close and re-open the database
1431 // without saving, then ask for another unique ID. Verify IDs are not reused.
1432 // This scenario simulates a crash within the first few seconds of operation.
TEST_F(SyncableDirectoryTest,LocalIdReuseTest)1433 TEST_F(SyncableDirectoryTest, LocalIdReuseTest) {
1434 Id pre_crash_id = dir_->NextId();
1435 SimulateCrashAndReloadDir();
1436 Id post_crash_id = dir_->NextId();
1437 EXPECT_NE(pre_crash_id, post_crash_id);
1438 }
1439
1440 // Ask the directory to generate a unique ID. Save the directory. Close and
1441 // re-open the database without saving, then ask for another unique ID. Verify
1442 // IDs are not reused. This scenario simulates a steady-state crash.
TEST_F(SyncableDirectoryTest,LocalIdReuseTestWithSave)1443 TEST_F(SyncableDirectoryTest, LocalIdReuseTestWithSave) {
1444 Id pre_crash_id = dir_->NextId();
1445 dir_->SaveChanges();
1446 SimulateCrashAndReloadDir();
1447 Id post_crash_id = dir_->NextId();
1448 EXPECT_NE(pre_crash_id, post_crash_id);
1449 }
1450
1451 // Ensure that the unsynced, is_del and server unkown entries that may have been
1452 // left in the database by old clients will be deleted when we open the old
1453 // database.
TEST_F(SyncableDirectoryTest,OldClientLeftUnsyncedDeletedLocalItem)1454 TEST_F(SyncableDirectoryTest, OldClientLeftUnsyncedDeletedLocalItem) {
1455 // We must create an entry with the offending properties. This is done with
1456 // some abuse of the MutableEntry's API; it doesn't expect us to modify an
1457 // item after it is deleted. If this hack becomes impractical we will need to
1458 // find a new way to simulate this scenario.
1459
1460 TestIdFactory id_factory;
1461
1462 // Happy-path: These valid entries should not get deleted.
1463 Id server_knows_id = id_factory.NewServerId();
1464 Id not_is_del_id = id_factory.NewLocalId();
1465
1466 // The ID of the entry which will be unsynced, is_del and !ServerKnows().
1467 Id zombie_id = id_factory.NewLocalId();
1468
1469 // We're about to do some bad things. Tell the directory verification
1470 // routines to look the other way.
1471 dir_->SetInvariantCheckLevel(OFF);
1472
1473 {
1474 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1475
1476 // Create an uncommitted tombstone entry.
1477 MutableEntry server_knows(&trans, CREATE, BOOKMARKS, id_factory.root(),
1478 "server_knows");
1479 server_knows.PutId(server_knows_id);
1480 server_knows.PutIsUnsynced(true);
1481 server_knows.PutIsDel(true);
1482 server_knows.PutBaseVersion(5);
1483 server_knows.PutServerVersion(4);
1484
1485 // Create a valid update entry.
1486 MutableEntry not_is_del(
1487 &trans, CREATE, BOOKMARKS, id_factory.root(), "not_is_del");
1488 not_is_del.PutId(not_is_del_id);
1489 not_is_del.PutIsDel(false);
1490 not_is_del.PutIsUnsynced(true);
1491
1492 // Create a tombstone which should never be sent to the server because the
1493 // server never knew about the item's existence.
1494 //
1495 // New clients should never put entries into this state. We work around
1496 // this by setting IS_DEL before setting IS_UNSYNCED, something which the
1497 // client should never do in practice.
1498 MutableEntry zombie(&trans, CREATE, BOOKMARKS, id_factory.root(), "zombie");
1499 zombie.PutId(zombie_id);
1500 zombie.PutIsDel(true);
1501 zombie.PutIsUnsynced(true);
1502 }
1503
1504 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir());
1505
1506 {
1507 ReadTransaction trans(FROM_HERE, dir_.get());
1508
1509 // The directory loading routines should have cleaned things up, making it
1510 // safe to check invariants once again.
1511 dir_->FullyCheckTreeInvariants(&trans);
1512
1513 Entry server_knows(&trans, GET_BY_ID, server_knows_id);
1514 EXPECT_TRUE(server_knows.good());
1515
1516 Entry not_is_del(&trans, GET_BY_ID, not_is_del_id);
1517 EXPECT_TRUE(not_is_del.good());
1518
1519 Entry zombie(&trans, GET_BY_ID, zombie_id);
1520 EXPECT_FALSE(zombie.good());
1521 }
1522 }
1523
TEST_F(SyncableDirectoryTest,PositionWithNullSurvivesSaveAndReload)1524 TEST_F(SyncableDirectoryTest, PositionWithNullSurvivesSaveAndReload) {
1525 TestIdFactory id_factory;
1526 Id null_child_id;
1527 const char null_cstr[] = "\0null\0test";
1528 std::string null_str(null_cstr, arraysize(null_cstr) - 1);
1529 // Pad up to the minimum length with 0x7f characters, then add a string that
1530 // contains a few NULLs to the end. This is slightly wrong, since the suffix
1531 // part of a UniquePosition shouldn't contain NULLs, but it's good enough for
1532 // this test.
1533 std::string suffix =
1534 std::string(UniquePosition::kSuffixLength - null_str.length(), '\x7f')
1535 + null_str;
1536 UniquePosition null_pos = UniquePosition::FromInt64(10, suffix);
1537
1538 {
1539 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1540
1541 MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent");
1542 parent.PutIsDir(true);
1543 parent.PutIsUnsynced(true);
1544
1545 MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child");
1546 child.PutIsUnsynced(true);
1547 child.PutUniquePosition(null_pos);
1548 child.PutServerUniquePosition(null_pos);
1549
1550 null_child_id = child.GetId();
1551 }
1552
1553 EXPECT_EQ(OPENED, SimulateSaveAndReloadDir());
1554
1555 {
1556 ReadTransaction trans(FROM_HERE, dir_.get());
1557
1558 Entry null_ordinal_child(&trans, GET_BY_ID, null_child_id);
1559 EXPECT_TRUE(
1560 null_pos.Equals(null_ordinal_child.GetUniquePosition()));
1561 EXPECT_TRUE(
1562 null_pos.Equals(null_ordinal_child.GetServerUniquePosition()));
1563 }
1564 }
1565
1566 // An OnDirectoryBackingStore that can be set to always fail SaveChanges.
1567 class TestBackingStore : public OnDiskDirectoryBackingStore {
1568 public:
1569 TestBackingStore(const std::string& dir_name,
1570 const base::FilePath& backing_filepath);
1571
1572 virtual ~TestBackingStore();
1573
1574 virtual bool SaveChanges(const Directory::SaveChangesSnapshot& snapshot)
1575 OVERRIDE;
1576
StartFailingSaveChanges()1577 void StartFailingSaveChanges() {
1578 fail_save_changes_ = true;
1579 }
1580
1581 private:
1582 bool fail_save_changes_;
1583 };
1584
TestBackingStore(const std::string & dir_name,const base::FilePath & backing_filepath)1585 TestBackingStore::TestBackingStore(const std::string& dir_name,
1586 const base::FilePath& backing_filepath)
1587 : OnDiskDirectoryBackingStore(dir_name, backing_filepath),
1588 fail_save_changes_(false) {
1589 }
1590
~TestBackingStore()1591 TestBackingStore::~TestBackingStore() { }
1592
SaveChanges(const Directory::SaveChangesSnapshot & snapshot)1593 bool TestBackingStore::SaveChanges(
1594 const Directory::SaveChangesSnapshot& snapshot){
1595 if (fail_save_changes_) {
1596 return false;
1597 } else {
1598 return OnDiskDirectoryBackingStore::SaveChanges(snapshot);
1599 }
1600 }
1601
1602 // A directory whose Save() function can be set to always fail.
1603 class TestDirectory : public Directory {
1604 public:
1605 // A factory function used to work around some initialization order issues.
1606 static TestDirectory* Create(
1607 Encryptor *encryptor,
1608 UnrecoverableErrorHandler *handler,
1609 const std::string& dir_name,
1610 const base::FilePath& backing_filepath);
1611
1612 virtual ~TestDirectory();
1613
StartFailingSaveChanges()1614 void StartFailingSaveChanges() {
1615 backing_store_->StartFailingSaveChanges();
1616 }
1617
1618 private:
1619 TestDirectory(Encryptor* encryptor,
1620 UnrecoverableErrorHandler* handler,
1621 TestBackingStore* backing_store);
1622
1623 TestBackingStore* backing_store_;
1624 };
1625
Create(Encryptor * encryptor,UnrecoverableErrorHandler * handler,const std::string & dir_name,const base::FilePath & backing_filepath)1626 TestDirectory* TestDirectory::Create(
1627 Encryptor *encryptor,
1628 UnrecoverableErrorHandler *handler,
1629 const std::string& dir_name,
1630 const base::FilePath& backing_filepath) {
1631 TestBackingStore* backing_store =
1632 new TestBackingStore(dir_name, backing_filepath);
1633 return new TestDirectory(encryptor, handler, backing_store);
1634 }
1635
TestDirectory(Encryptor * encryptor,UnrecoverableErrorHandler * handler,TestBackingStore * backing_store)1636 TestDirectory::TestDirectory(Encryptor* encryptor,
1637 UnrecoverableErrorHandler* handler,
1638 TestBackingStore* backing_store)
1639 : Directory(backing_store, handler, NULL, NULL, NULL),
1640 backing_store_(backing_store) {
1641 }
1642
~TestDirectory()1643 TestDirectory::~TestDirectory() { }
1644
TEST(OnDiskSyncableDirectory,FailInitialWrite)1645 TEST(OnDiskSyncableDirectory, FailInitialWrite) {
1646 FakeEncryptor encryptor;
1647 TestUnrecoverableErrorHandler handler;
1648 base::ScopedTempDir temp_dir;
1649 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
1650 base::FilePath file_path = temp_dir.path().Append(
1651 FILE_PATH_LITERAL("Test.sqlite3"));
1652 std::string name = "user@x.com";
1653 NullDirectoryChangeDelegate delegate;
1654
1655 scoped_ptr<TestDirectory> test_dir(
1656 TestDirectory::Create(&encryptor, &handler, name, file_path));
1657
1658 test_dir->StartFailingSaveChanges();
1659 ASSERT_EQ(FAILED_INITIAL_WRITE, test_dir->Open(name, &delegate,
1660 NullTransactionObserver()));
1661 }
1662
1663 // A variant of SyncableDirectoryTest that uses a real sqlite database.
1664 class OnDiskSyncableDirectoryTest : public SyncableDirectoryTest {
1665 protected:
1666 // SetUp() is called before each test case is run.
1667 // The sqlite3 DB is deleted before each test is run.
SetUp()1668 virtual void SetUp() {
1669 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
1670 file_path_ = temp_dir_.path().Append(
1671 FILE_PATH_LITERAL("Test.sqlite3"));
1672 base::DeleteFile(file_path_, true);
1673 CreateDirectory();
1674 }
1675
TearDown()1676 virtual void TearDown() {
1677 // This also closes file handles.
1678 dir_->SaveChanges();
1679 dir_.reset();
1680 base::DeleteFile(file_path_, true);
1681 }
1682
1683 // Creates a new directory. Deletes the old directory, if it exists.
CreateDirectory()1684 void CreateDirectory() {
1685 test_directory_ =
1686 TestDirectory::Create(&encryptor_, &handler_, kName, file_path_);
1687 dir_.reset(test_directory_);
1688 ASSERT_TRUE(dir_.get());
1689 ASSERT_EQ(OPENED, dir_->Open(kName, &delegate_,
1690 NullTransactionObserver()));
1691 ASSERT_TRUE(dir_->good());
1692 }
1693
SaveAndReloadDir()1694 void SaveAndReloadDir() {
1695 dir_->SaveChanges();
1696 CreateDirectory();
1697 }
1698
StartFailingSaveChanges()1699 void StartFailingSaveChanges() {
1700 test_directory_->StartFailingSaveChanges();
1701 }
1702
1703 TestDirectory *test_directory_; // mirrors scoped_ptr<Directory> dir_
1704 base::ScopedTempDir temp_dir_;
1705 base::FilePath file_path_;
1706 };
1707
TEST_F(OnDiskSyncableDirectoryTest,TestPurgeEntriesWithTypeIn)1708 TEST_F(OnDiskSyncableDirectoryTest, TestPurgeEntriesWithTypeIn) {
1709 sync_pb::EntitySpecifics bookmark_specs;
1710 sync_pb::EntitySpecifics autofill_specs;
1711 sync_pb::EntitySpecifics preference_specs;
1712 AddDefaultFieldValue(BOOKMARKS, &bookmark_specs);
1713 AddDefaultFieldValue(PREFERENCES, &preference_specs);
1714 AddDefaultFieldValue(AUTOFILL, &autofill_specs);
1715
1716 ModelTypeSet types_to_purge(PREFERENCES, AUTOFILL);
1717
1718 TestIdFactory id_factory;
1719 // Create some items for each type.
1720 {
1721 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1722
1723 // Make it look like these types have completed initial sync.
1724 CreateTypeRoot(&trans, dir_.get(), BOOKMARKS);
1725 CreateTypeRoot(&trans, dir_.get(), PREFERENCES);
1726 CreateTypeRoot(&trans, dir_.get(), AUTOFILL);
1727
1728 // Add more nodes for this type. Technically, they should be placed under
1729 // the proper type root nodes but the assertions in this test won't notice
1730 // if their parent isn't quite right.
1731 MutableEntry item1(&trans, CREATE, BOOKMARKS, trans.root_id(), "Item");
1732 ASSERT_TRUE(item1.good());
1733 item1.PutServerSpecifics(bookmark_specs);
1734 item1.PutIsUnsynced(true);
1735
1736 MutableEntry item2(&trans, CREATE_NEW_UPDATE_ITEM,
1737 id_factory.NewServerId());
1738 ASSERT_TRUE(item2.good());
1739 item2.PutServerSpecifics(bookmark_specs);
1740 item2.PutIsUnappliedUpdate(true);
1741
1742 MutableEntry item3(&trans, CREATE, PREFERENCES,
1743 trans.root_id(), "Item");
1744 ASSERT_TRUE(item3.good());
1745 item3.PutSpecifics(preference_specs);
1746 item3.PutServerSpecifics(preference_specs);
1747 item3.PutIsUnsynced(true);
1748
1749 MutableEntry item4(&trans, CREATE_NEW_UPDATE_ITEM,
1750 id_factory.NewServerId());
1751 ASSERT_TRUE(item4.good());
1752 item4.PutServerSpecifics(preference_specs);
1753 item4.PutIsUnappliedUpdate(true);
1754
1755 MutableEntry item5(&trans, CREATE, AUTOFILL,
1756 trans.root_id(), "Item");
1757 ASSERT_TRUE(item5.good());
1758 item5.PutSpecifics(autofill_specs);
1759 item5.PutServerSpecifics(autofill_specs);
1760 item5.PutIsUnsynced(true);
1761
1762 MutableEntry item6(&trans, CREATE_NEW_UPDATE_ITEM,
1763 id_factory.NewServerId());
1764 ASSERT_TRUE(item6.good());
1765 item6.PutServerSpecifics(autofill_specs);
1766 item6.PutIsUnappliedUpdate(true);
1767 }
1768
1769 dir_->SaveChanges();
1770 {
1771 ReadTransaction trans(FROM_HERE, dir_.get());
1772 MetahandleSet all_set;
1773 GetAllMetaHandles(&trans, &all_set);
1774 ASSERT_EQ(10U, all_set.size());
1775 }
1776
1777 dir_->PurgeEntriesWithTypeIn(types_to_purge, ModelTypeSet(), ModelTypeSet());
1778
1779 // We first query the in-memory data, and then reload the directory (without
1780 // saving) to verify that disk does not still have the data.
1781 CheckPurgeEntriesWithTypeInSucceeded(types_to_purge, true);
1782 SaveAndReloadDir();
1783 CheckPurgeEntriesWithTypeInSucceeded(types_to_purge, false);
1784 }
1785
TEST_F(OnDiskSyncableDirectoryTest,TestShareInfo)1786 TEST_F(OnDiskSyncableDirectoryTest, TestShareInfo) {
1787 dir_->set_store_birthday("Jan 31st");
1788 const char* const bag_of_chips_array = "\0bag of chips";
1789 const std::string bag_of_chips_string =
1790 std::string(bag_of_chips_array, sizeof(bag_of_chips_array));
1791 dir_->set_bag_of_chips(bag_of_chips_string);
1792 {
1793 ReadTransaction trans(FROM_HERE, dir_.get());
1794 EXPECT_EQ("Jan 31st", dir_->store_birthday());
1795 EXPECT_EQ(bag_of_chips_string, dir_->bag_of_chips());
1796 }
1797 dir_->set_store_birthday("April 10th");
1798 const char* const bag_of_chips2_array = "\0bag of chips2";
1799 const std::string bag_of_chips2_string =
1800 std::string(bag_of_chips2_array, sizeof(bag_of_chips2_array));
1801 dir_->set_bag_of_chips(bag_of_chips2_string);
1802 dir_->SaveChanges();
1803 {
1804 ReadTransaction trans(FROM_HERE, dir_.get());
1805 EXPECT_EQ("April 10th", dir_->store_birthday());
1806 EXPECT_EQ(bag_of_chips2_string, dir_->bag_of_chips());
1807 }
1808 const char* const bag_of_chips3_array = "\0bag of chips3";
1809 const std::string bag_of_chips3_string =
1810 std::string(bag_of_chips3_array, sizeof(bag_of_chips3_array));
1811 dir_->set_bag_of_chips(bag_of_chips3_string);
1812 // Restore the directory from disk. Make sure that nothing's changed.
1813 SaveAndReloadDir();
1814 {
1815 ReadTransaction trans(FROM_HERE, dir_.get());
1816 EXPECT_EQ("April 10th", dir_->store_birthday());
1817 EXPECT_EQ(bag_of_chips3_string, dir_->bag_of_chips());
1818 }
1819 }
1820
TEST_F(OnDiskSyncableDirectoryTest,TestSimpleFieldsPreservedDuringSaveChanges)1821 TEST_F(OnDiskSyncableDirectoryTest,
1822 TestSimpleFieldsPreservedDuringSaveChanges) {
1823 Id update_id = TestIdFactory::FromNumber(1);
1824 Id create_id;
1825 EntryKernel create_pre_save, update_pre_save;
1826 EntryKernel create_post_save, update_post_save;
1827 std::string create_name = "Create";
1828
1829 {
1830 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1831 MutableEntry create(
1832 &trans, CREATE, BOOKMARKS, trans.root_id(), create_name);
1833 MutableEntry update(&trans, CREATE_NEW_UPDATE_ITEM, update_id);
1834 create.PutIsUnsynced(true);
1835 update.PutIsUnappliedUpdate(true);
1836 sync_pb::EntitySpecifics specifics;
1837 specifics.mutable_bookmark()->set_favicon("PNG");
1838 specifics.mutable_bookmark()->set_url("http://nowhere");
1839 create.PutSpecifics(specifics);
1840 update.PutSpecifics(specifics);
1841 create_pre_save = create.GetKernelCopy();
1842 update_pre_save = update.GetKernelCopy();
1843 create_id = create.GetId();
1844 }
1845
1846 dir_->SaveChanges();
1847 dir_.reset(new Directory(new OnDiskDirectoryBackingStore(kName, file_path_),
1848 &handler_,
1849 NULL,
1850 NULL,
1851 NULL));
1852
1853 ASSERT_TRUE(dir_.get());
1854 ASSERT_EQ(OPENED, dir_->Open(kName, &delegate_, NullTransactionObserver()));
1855 ASSERT_TRUE(dir_->good());
1856
1857 {
1858 ReadTransaction trans(FROM_HERE, dir_.get());
1859 Entry create(&trans, GET_BY_ID, create_id);
1860 EXPECT_EQ(1, CountEntriesWithName(&trans, trans.root_id(), create_name));
1861 Entry update(&trans, GET_BY_ID, update_id);
1862 create_post_save = create.GetKernelCopy();
1863 update_post_save = update.GetKernelCopy();
1864 }
1865 int i = BEGIN_FIELDS;
1866 for ( ; i < INT64_FIELDS_END ; ++i) {
1867 EXPECT_EQ(create_pre_save.ref((Int64Field)i) +
1868 (i == TRANSACTION_VERSION ? 1 : 0),
1869 create_post_save.ref((Int64Field)i))
1870 << "int64 field #" << i << " changed during save/load";
1871 EXPECT_EQ(update_pre_save.ref((Int64Field)i) +
1872 (i == TRANSACTION_VERSION ? 1 : 0),
1873 update_post_save.ref((Int64Field)i))
1874 << "int64 field #" << i << " changed during save/load";
1875 }
1876 for ( ; i < TIME_FIELDS_END ; ++i) {
1877 EXPECT_EQ(create_pre_save.ref((TimeField)i),
1878 create_post_save.ref((TimeField)i))
1879 << "time field #" << i << " changed during save/load";
1880 EXPECT_EQ(update_pre_save.ref((TimeField)i),
1881 update_post_save.ref((TimeField)i))
1882 << "time field #" << i << " changed during save/load";
1883 }
1884 for ( ; i < ID_FIELDS_END ; ++i) {
1885 EXPECT_EQ(create_pre_save.ref((IdField)i),
1886 create_post_save.ref((IdField)i))
1887 << "id field #" << i << " changed during save/load";
1888 EXPECT_EQ(update_pre_save.ref((IdField)i),
1889 update_pre_save.ref((IdField)i))
1890 << "id field #" << i << " changed during save/load";
1891 }
1892 for ( ; i < BIT_FIELDS_END ; ++i) {
1893 EXPECT_EQ(create_pre_save.ref((BitField)i),
1894 create_post_save.ref((BitField)i))
1895 << "Bit field #" << i << " changed during save/load";
1896 EXPECT_EQ(update_pre_save.ref((BitField)i),
1897 update_post_save.ref((BitField)i))
1898 << "Bit field #" << i << " changed during save/load";
1899 }
1900 for ( ; i < STRING_FIELDS_END ; ++i) {
1901 EXPECT_EQ(create_pre_save.ref((StringField)i),
1902 create_post_save.ref((StringField)i))
1903 << "String field #" << i << " changed during save/load";
1904 EXPECT_EQ(update_pre_save.ref((StringField)i),
1905 update_post_save.ref((StringField)i))
1906 << "String field #" << i << " changed during save/load";
1907 }
1908 for ( ; i < PROTO_FIELDS_END; ++i) {
1909 EXPECT_EQ(create_pre_save.ref((ProtoField)i).SerializeAsString(),
1910 create_post_save.ref((ProtoField)i).SerializeAsString())
1911 << "Blob field #" << i << " changed during save/load";
1912 EXPECT_EQ(update_pre_save.ref((ProtoField)i).SerializeAsString(),
1913 update_post_save.ref((ProtoField)i).SerializeAsString())
1914 << "Blob field #" << i << " changed during save/load";
1915 }
1916 for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) {
1917 EXPECT_TRUE(create_pre_save.ref((UniquePositionField)i).Equals(
1918 create_post_save.ref((UniquePositionField)i)))
1919 << "Position field #" << i << " changed during save/load";
1920 EXPECT_TRUE(update_pre_save.ref((UniquePositionField)i).Equals(
1921 update_post_save.ref((UniquePositionField)i)))
1922 << "Position field #" << i << " changed during save/load";
1923 }
1924 }
1925
TEST_F(OnDiskSyncableDirectoryTest,TestSaveChangesFailure)1926 TEST_F(OnDiskSyncableDirectoryTest, TestSaveChangesFailure) {
1927 int64 handle1 = 0;
1928 // Set up an item using a regular, saveable directory.
1929 {
1930 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1931
1932 MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "aguilera");
1933 ASSERT_TRUE(e1.good());
1934 EXPECT_TRUE(e1.GetKernelCopy().is_dirty());
1935 handle1 = e1.GetMetahandle();
1936 e1.PutBaseVersion(1);
1937 e1.PutIsDir(true);
1938 e1.PutId(TestIdFactory::FromNumber(101));
1939 EXPECT_TRUE(e1.GetKernelCopy().is_dirty());
1940 EXPECT_TRUE(IsInDirtyMetahandles(handle1));
1941 }
1942 ASSERT_TRUE(dir_->SaveChanges());
1943
1944 // Make sure the item is no longer dirty after saving,
1945 // and make a modification.
1946 {
1947 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1948
1949 MutableEntry aguilera(&trans, GET_BY_HANDLE, handle1);
1950 ASSERT_TRUE(aguilera.good());
1951 EXPECT_FALSE(aguilera.GetKernelCopy().is_dirty());
1952 EXPECT_EQ(aguilera.GetNonUniqueName(), "aguilera");
1953 aguilera.PutNonUniqueName("overwritten");
1954 EXPECT_TRUE(aguilera.GetKernelCopy().is_dirty());
1955 EXPECT_TRUE(IsInDirtyMetahandles(handle1));
1956 }
1957 ASSERT_TRUE(dir_->SaveChanges());
1958
1959 // Now do some operations when SaveChanges() will fail.
1960 StartFailingSaveChanges();
1961 ASSERT_TRUE(dir_->good());
1962
1963 int64 handle2 = 0;
1964 {
1965 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
1966
1967 MutableEntry aguilera(&trans, GET_BY_HANDLE, handle1);
1968 ASSERT_TRUE(aguilera.good());
1969 EXPECT_FALSE(aguilera.GetKernelCopy().is_dirty());
1970 EXPECT_EQ(aguilera.GetNonUniqueName(), "overwritten");
1971 EXPECT_FALSE(aguilera.GetKernelCopy().is_dirty());
1972 EXPECT_FALSE(IsInDirtyMetahandles(handle1));
1973 aguilera.PutNonUniqueName("christina");
1974 EXPECT_TRUE(aguilera.GetKernelCopy().is_dirty());
1975 EXPECT_TRUE(IsInDirtyMetahandles(handle1));
1976
1977 // New item.
1978 MutableEntry kids_on_block(
1979 &trans, CREATE, BOOKMARKS, trans.root_id(), "kids");
1980 ASSERT_TRUE(kids_on_block.good());
1981 handle2 = kids_on_block.GetMetahandle();
1982 kids_on_block.PutBaseVersion(1);
1983 kids_on_block.PutIsDir(true);
1984 kids_on_block.PutId(TestIdFactory::FromNumber(102));
1985 EXPECT_TRUE(kids_on_block.GetKernelCopy().is_dirty());
1986 EXPECT_TRUE(IsInDirtyMetahandles(handle2));
1987 }
1988
1989 // We are using an unsaveable directory, so this can't succeed. However,
1990 // the HandleSaveChangesFailure code path should have been triggered.
1991 ASSERT_FALSE(dir_->SaveChanges());
1992
1993 // Make sure things were rolled back and the world is as it was before call.
1994 {
1995 ReadTransaction trans(FROM_HERE, dir_.get());
1996 Entry e1(&trans, GET_BY_HANDLE, handle1);
1997 ASSERT_TRUE(e1.good());
1998 EntryKernel aguilera = e1.GetKernelCopy();
1999 Entry kids(&trans, GET_BY_HANDLE, handle2);
2000 ASSERT_TRUE(kids.good());
2001 EXPECT_TRUE(kids.GetKernelCopy().is_dirty());
2002 EXPECT_TRUE(IsInDirtyMetahandles(handle2));
2003 EXPECT_TRUE(aguilera.is_dirty());
2004 EXPECT_TRUE(IsInDirtyMetahandles(handle1));
2005 }
2006 }
2007
TEST_F(OnDiskSyncableDirectoryTest,TestSaveChangesFailureWithPurge)2008 TEST_F(OnDiskSyncableDirectoryTest, TestSaveChangesFailureWithPurge) {
2009 int64 handle1 = 0;
2010 // Set up an item using a regular, saveable directory.
2011 {
2012 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
2013
2014 MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "aguilera");
2015 ASSERT_TRUE(e1.good());
2016 EXPECT_TRUE(e1.GetKernelCopy().is_dirty());
2017 handle1 = e1.GetMetahandle();
2018 e1.PutBaseVersion(1);
2019 e1.PutIsDir(true);
2020 e1.PutId(TestIdFactory::FromNumber(101));
2021 sync_pb::EntitySpecifics bookmark_specs;
2022 AddDefaultFieldValue(BOOKMARKS, &bookmark_specs);
2023 e1.PutSpecifics(bookmark_specs);
2024 e1.PutServerSpecifics(bookmark_specs);
2025 e1.PutId(TestIdFactory::FromNumber(101));
2026 EXPECT_TRUE(e1.GetKernelCopy().is_dirty());
2027 EXPECT_TRUE(IsInDirtyMetahandles(handle1));
2028 }
2029 ASSERT_TRUE(dir_->SaveChanges());
2030
2031 // Now do some operations while SaveChanges() is set to fail.
2032 StartFailingSaveChanges();
2033 ASSERT_TRUE(dir_->good());
2034
2035 ModelTypeSet set(BOOKMARKS);
2036 dir_->PurgeEntriesWithTypeIn(set, ModelTypeSet(), ModelTypeSet());
2037 EXPECT_TRUE(IsInMetahandlesToPurge(handle1));
2038 ASSERT_FALSE(dir_->SaveChanges());
2039 EXPECT_TRUE(IsInMetahandlesToPurge(handle1));
2040 }
2041
2042 } // namespace
2043
ValidateEntry(BaseTransaction * trans,int64 id,bool check_name,const std::string & name,int64 base_version,int64 server_version,bool is_del)2044 void SyncableDirectoryTest::ValidateEntry(BaseTransaction* trans,
2045 int64 id,
2046 bool check_name,
2047 const std::string& name,
2048 int64 base_version,
2049 int64 server_version,
2050 bool is_del) {
2051 Entry e(trans, GET_BY_ID, TestIdFactory::FromNumber(id));
2052 ASSERT_TRUE(e.good());
2053 if (check_name)
2054 ASSERT_TRUE(name == e.GetNonUniqueName());
2055 ASSERT_TRUE(base_version == e.GetBaseVersion());
2056 ASSERT_TRUE(server_version == e.GetServerVersion());
2057 ASSERT_TRUE(is_del == e.GetIsDel());
2058 }
2059
SimulateSaveAndReloadDir()2060 DirOpenResult SyncableDirectoryTest::SimulateSaveAndReloadDir() {
2061 if (!dir_->SaveChanges())
2062 return FAILED_IN_UNITTEST;
2063
2064 return ReloadDirImpl();
2065 }
2066
SimulateCrashAndReloadDir()2067 DirOpenResult SyncableDirectoryTest::SimulateCrashAndReloadDir() {
2068 return ReloadDirImpl();
2069 }
2070
ReloadDirImpl()2071 DirOpenResult SyncableDirectoryTest::ReloadDirImpl() {
2072 // Do some tricky things to preserve the backing store.
2073 DirectoryBackingStore* saved_store = dir_->store_.release();
2074
2075 // Close the current directory.
2076 dir_->Close();
2077 dir_.reset();
2078
2079 dir_.reset(new Directory(saved_store,
2080 &handler_,
2081 NULL,
2082 NULL,
2083 NULL));
2084 DirOpenResult result = dir_->OpenImpl(kName, &delegate_,
2085 NullTransactionObserver());
2086
2087 // If something went wrong, we need to clear this member. If we don't,
2088 // TearDown() will be guaranteed to crash when it calls SaveChanges().
2089 if (result != OPENED)
2090 dir_.reset();
2091
2092 return result;
2093 }
2094
2095 namespace {
2096
2097 class SyncableDirectoryManagement : public testing::Test {
2098 public:
SetUp()2099 virtual void SetUp() {
2100 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
2101 }
2102
TearDown()2103 virtual void TearDown() {
2104 }
2105 protected:
2106 base::MessageLoop message_loop_;
2107 base::ScopedTempDir temp_dir_;
2108 FakeEncryptor encryptor_;
2109 TestUnrecoverableErrorHandler handler_;
2110 NullDirectoryChangeDelegate delegate_;
2111 };
2112
TEST_F(SyncableDirectoryManagement,TestFileRelease)2113 TEST_F(SyncableDirectoryManagement, TestFileRelease) {
2114 base::FilePath path = temp_dir_.path().Append(
2115 Directory::kSyncDatabaseFilename);
2116
2117 syncable::Directory dir(new OnDiskDirectoryBackingStore("ScopeTest", path),
2118 &handler_,
2119 NULL,
2120 NULL,
2121 NULL);
2122 DirOpenResult result =
2123 dir.Open("ScopeTest", &delegate_, NullTransactionObserver());
2124 ASSERT_EQ(result, OPENED);
2125 dir.Close();
2126
2127 // Closing the directory should have released the backing database file.
2128 ASSERT_TRUE(base::DeleteFile(path, true));
2129 }
2130
2131 class StressTransactionsDelegate : public base::PlatformThread::Delegate {
2132 public:
StressTransactionsDelegate(Directory * dir,int thread_number)2133 StressTransactionsDelegate(Directory* dir, int thread_number)
2134 : dir_(dir),
2135 thread_number_(thread_number) {}
2136
2137 private:
2138 Directory* const dir_;
2139 const int thread_number_;
2140
2141 // PlatformThread::Delegate methods:
ThreadMain()2142 virtual void ThreadMain() OVERRIDE {
2143 int entry_count = 0;
2144 std::string path_name;
2145
2146 for (int i = 0; i < 20; ++i) {
2147 const int rand_action = rand() % 10;
2148 if (rand_action < 4 && !path_name.empty()) {
2149 ReadTransaction trans(FROM_HERE, dir_);
2150 CHECK(1 == CountEntriesWithName(&trans, trans.root_id(), path_name));
2151 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(
2152 rand() % 10));
2153 } else {
2154 std::string unique_name =
2155 base::StringPrintf("%d.%d", thread_number_, entry_count++);
2156 path_name.assign(unique_name.begin(), unique_name.end());
2157 WriteTransaction trans(FROM_HERE, UNITTEST, dir_);
2158 MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), path_name);
2159 CHECK(e.good());
2160 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(
2161 rand() % 20));
2162 e.PutIsUnsynced(true);
2163 if (e.PutId(TestIdFactory::FromNumber(rand())) &&
2164 e.GetId().ServerKnows() && !e.GetId().IsRoot()) {
2165 e.PutBaseVersion(1);
2166 }
2167 }
2168 }
2169 }
2170
2171 DISALLOW_COPY_AND_ASSIGN(StressTransactionsDelegate);
2172 };
2173
TEST(SyncableDirectory,StressTransactions)2174 TEST(SyncableDirectory, StressTransactions) {
2175 base::MessageLoop message_loop;
2176 base::ScopedTempDir temp_dir;
2177 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
2178 FakeEncryptor encryptor;
2179 TestUnrecoverableErrorHandler handler;
2180 NullDirectoryChangeDelegate delegate;
2181 std::string dirname = "stress";
2182 Directory dir(new InMemoryDirectoryBackingStore(dirname),
2183 &handler,
2184 NULL,
2185 NULL,
2186 NULL);
2187 dir.Open(dirname, &delegate, NullTransactionObserver());
2188
2189 const int kThreadCount = 7;
2190 base::PlatformThreadHandle threads[kThreadCount];
2191 scoped_ptr<StressTransactionsDelegate> thread_delegates[kThreadCount];
2192
2193 for (int i = 0; i < kThreadCount; ++i) {
2194 thread_delegates[i].reset(new StressTransactionsDelegate(&dir, i));
2195 ASSERT_TRUE(base::PlatformThread::Create(
2196 0, thread_delegates[i].get(), &threads[i]));
2197 }
2198
2199 for (int i = 0; i < kThreadCount; ++i) {
2200 base::PlatformThread::Join(threads[i]);
2201 }
2202
2203 dir.Close();
2204 }
2205
2206 class SyncableClientTagTest : public SyncableDirectoryTest {
2207 public:
2208 static const int kBaseVersion = 1;
2209 const char* test_name_;
2210 const char* test_tag_;
2211
SyncableClientTagTest()2212 SyncableClientTagTest() : test_name_("test_name"), test_tag_("dietcoke") {}
2213
CreateWithDefaultTag(Id id,bool deleted)2214 bool CreateWithDefaultTag(Id id, bool deleted) {
2215 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get());
2216 MutableEntry me(&wtrans, CREATE, PREFERENCES,
2217 wtrans.root_id(), test_name_);
2218 CHECK(me.good());
2219 me.PutId(id);
2220 if (id.ServerKnows()) {
2221 me.PutBaseVersion(kBaseVersion);
2222 }
2223 me.PutIsUnsynced(true);
2224 me.PutIsDel(deleted);
2225 me.PutIsDir(false);
2226 return me.PutUniqueClientTag(test_tag_);
2227 }
2228
2229 // Verify an entry exists with the default tag.
VerifyTag(Id id,bool deleted)2230 void VerifyTag(Id id, bool deleted) {
2231 // Should still be present and valid in the client tag index.
2232 ReadTransaction trans(FROM_HERE, dir_.get());
2233 Entry me(&trans, GET_BY_CLIENT_TAG, test_tag_);
2234 CHECK(me.good());
2235 EXPECT_EQ(me.GetId(), id);
2236 EXPECT_EQ(me.GetUniqueClientTag(), test_tag_);
2237 EXPECT_EQ(me.GetIsDel(), deleted);
2238
2239 // We only sync deleted items that the server knew about.
2240 if (me.GetId().ServerKnows() || !me.GetIsDel()) {
2241 EXPECT_EQ(me.GetIsUnsynced(), true);
2242 }
2243 }
2244
2245 protected:
2246 TestIdFactory factory_;
2247 };
2248
TEST_F(SyncableClientTagTest,TestClientTagClear)2249 TEST_F(SyncableClientTagTest, TestClientTagClear) {
2250 Id server_id = factory_.NewServerId();
2251 EXPECT_TRUE(CreateWithDefaultTag(server_id, false));
2252 {
2253 WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get());
2254 MutableEntry me(&trans, GET_BY_CLIENT_TAG, test_tag_);
2255 EXPECT_TRUE(me.good());
2256 me.PutUniqueClientTag(std::string());
2257 }
2258 {
2259 ReadTransaction trans(FROM_HERE, dir_.get());
2260 Entry by_tag(&trans, GET_BY_CLIENT_TAG, test_tag_);
2261 EXPECT_FALSE(by_tag.good());
2262
2263 Entry by_id(&trans, GET_BY_ID, server_id);
2264 EXPECT_TRUE(by_id.good());
2265 EXPECT_TRUE(by_id.GetUniqueClientTag().empty());
2266 }
2267 }
2268
TEST_F(SyncableClientTagTest,TestClientTagIndexServerId)2269 TEST_F(SyncableClientTagTest, TestClientTagIndexServerId) {
2270 Id server_id = factory_.NewServerId();
2271 EXPECT_TRUE(CreateWithDefaultTag(server_id, false));
2272 VerifyTag(server_id, false);
2273 }
2274
TEST_F(SyncableClientTagTest,TestClientTagIndexClientId)2275 TEST_F(SyncableClientTagTest, TestClientTagIndexClientId) {
2276 Id client_id = factory_.NewLocalId();
2277 EXPECT_TRUE(CreateWithDefaultTag(client_id, false));
2278 VerifyTag(client_id, false);
2279 }
2280
TEST_F(SyncableClientTagTest,TestDeletedClientTagIndexClientId)2281 TEST_F(SyncableClientTagTest, TestDeletedClientTagIndexClientId) {
2282 Id client_id = factory_.NewLocalId();
2283 EXPECT_TRUE(CreateWithDefaultTag(client_id, true));
2284 VerifyTag(client_id, true);
2285 }
2286
TEST_F(SyncableClientTagTest,TestDeletedClientTagIndexServerId)2287 TEST_F(SyncableClientTagTest, TestDeletedClientTagIndexServerId) {
2288 Id server_id = factory_.NewServerId();
2289 EXPECT_TRUE(CreateWithDefaultTag(server_id, true));
2290 VerifyTag(server_id, true);
2291 }
2292
TEST_F(SyncableClientTagTest,TestClientTagIndexDuplicateServer)2293 TEST_F(SyncableClientTagTest, TestClientTagIndexDuplicateServer) {
2294 EXPECT_TRUE(CreateWithDefaultTag(factory_.NewServerId(), true));
2295 EXPECT_FALSE(CreateWithDefaultTag(factory_.NewServerId(), true));
2296 EXPECT_FALSE(CreateWithDefaultTag(factory_.NewServerId(), false));
2297 EXPECT_FALSE(CreateWithDefaultTag(factory_.NewLocalId(), false));
2298 EXPECT_FALSE(CreateWithDefaultTag(factory_.NewLocalId(), true));
2299 }
2300
2301 } // namespace
2302 } // namespace syncable
2303 } // namespace syncer
2304