• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/engine/directory_commit_contribution.h"
6 
7 #include "base/message_loop/message_loop.h"
8 #include "sync/sessions/status_controller.h"
9 #include "sync/syncable/entry.h"
10 #include "sync/syncable/mutable_entry.h"
11 #include "sync/syncable/syncable_read_transaction.h"
12 #include "sync/syncable/syncable_write_transaction.h"
13 #include "sync/test/engine/test_directory_setter_upper.h"
14 #include "sync/test/engine/test_id_factory.h"
15 #include "sync/test/engine/test_syncable_utils.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 
18 namespace syncer {
19 
20 class DirectoryCommitContributionTest : public ::testing::Test {
21  public:
SetUp()22   virtual void SetUp() OVERRIDE {
23     dir_maker_.SetUp();
24 
25     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
26     CreateTypeRoot(&trans, dir(), PREFERENCES);
27     CreateTypeRoot(&trans, dir(), EXTENSIONS);
28     CreateTypeRoot(&trans, dir(), BOOKMARKS);
29   }
30 
TearDown()31   virtual void TearDown() OVERRIDE {
32     dir_maker_.TearDown();
33   }
34 
35  protected:
CreateUnsyncedItem(syncable::WriteTransaction * trans,ModelType type,const std::string & tag)36   int64 CreateUnsyncedItem(syncable::WriteTransaction* trans,
37                            ModelType type,
38                            const std::string& tag) {
39     syncable::Entry parent_entry(trans, syncable::GET_TYPE_ROOT, type);
40     syncable::MutableEntry entry(
41         trans,
42         syncable::CREATE,
43         type,
44         parent_entry.GetId(),
45         tag);
46     entry.PutIsUnsynced(true);
47     return entry.GetMetahandle();
48   }
49 
CreateSyncedItem(syncable::WriteTransaction * trans,ModelType type,const std::string & tag)50   int64 CreateSyncedItem(syncable::WriteTransaction* trans,
51                          ModelType type,
52                          const std::string& tag) {
53     syncable::Entry parent_entry(trans, syncable::GET_TYPE_ROOT, type);
54     syncable::MutableEntry entry(
55         trans,
56         syncable::CREATE,
57         type,
58         parent_entry.GetId(),
59         tag);
60 
61     entry.PutId(syncable::Id::CreateFromServerId(
62         id_factory_.NewServerId().GetServerId()));
63     entry.PutBaseVersion(10);
64     entry.PutServerVersion(10);
65     entry.PutIsUnappliedUpdate(false);
66     entry.PutIsUnsynced(false);
67     entry.PutIsDel(false);
68     entry.PutServerIsDel(false);
69 
70     return entry.GetMetahandle();
71   }
72 
CreateSuccessfulCommitResponse(const sync_pb::SyncEntity & entity,sync_pb::CommitResponse::EntryResponse * response)73   void CreateSuccessfulCommitResponse(
74       const sync_pb::SyncEntity& entity,
75       sync_pb::CommitResponse::EntryResponse* response) {
76     response->set_response_type(sync_pb::CommitResponse::SUCCESS);
77     response->set_non_unique_name(entity.name());
78     response->set_version(entity.version() + 1);
79     response->set_parent_id_string(entity.parent_id_string());
80 
81     if (entity.id_string()[0] == '-')  // Look for the - in 'c-1234' style IDs.
82       response->set_id_string(id_factory_.NewServerId().GetServerId());
83     else
84       response->set_id_string(entity.id_string());
85   }
86 
dir()87   syncable::Directory* dir() {
88     return dir_maker_.directory();
89   }
90 
91   TestIdFactory id_factory_;
92 
93   // Used in construction of DirectoryTypeDebugInfoEmitters.
94   ObserverList<TypeDebugInfoObserver> type_observers_;
95 
96  private:
97   base::MessageLoop loop_;  // Neeed to initialize the directory.
98   TestDirectorySetterUpper dir_maker_;
99 };
100 
101 // Verify that the DirectoryCommitContribution contains only entries of its
102 // specified type.
TEST_F(DirectoryCommitContributionTest,GatherByTypes)103 TEST_F(DirectoryCommitContributionTest, GatherByTypes) {
104   int64 pref1;
105   {
106     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
107     pref1 = CreateUnsyncedItem(&trans, PREFERENCES, "pref1");
108     CreateUnsyncedItem(&trans, PREFERENCES, "pref2");
109     CreateUnsyncedItem(&trans, EXTENSIONS, "extension1");
110   }
111 
112   DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_);
113   scoped_ptr<DirectoryCommitContribution> cc(
114       DirectoryCommitContribution::Build(dir(), PREFERENCES, 5, &emitter));
115   ASSERT_EQ(2U, cc->GetNumEntries());
116 
117   const std::vector<int64>& metahandles = cc->metahandles_;
118   EXPECT_TRUE(std::find(metahandles.begin(), metahandles.end(), pref1) !=
119               metahandles.end());
120   EXPECT_TRUE(std::find(metahandles.begin(), metahandles.end(), pref1) !=
121               metahandles.end());
122 
123   cc->CleanUp();
124 }
125 
126 // Verify that the DirectoryCommitContributionTest builder function
127 // truncates if necessary.
TEST_F(DirectoryCommitContributionTest,GatherAndTruncate)128 TEST_F(DirectoryCommitContributionTest, GatherAndTruncate) {
129   int64 pref1;
130   int64 pref2;
131   {
132     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
133     pref1 = CreateUnsyncedItem(&trans, PREFERENCES, "pref1");
134     pref2 = CreateUnsyncedItem(&trans, PREFERENCES, "pref2");
135     CreateUnsyncedItem(&trans, EXTENSIONS, "extension1");
136   }
137 
138   DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_);
139   scoped_ptr<DirectoryCommitContribution> cc(
140       DirectoryCommitContribution::Build(dir(), PREFERENCES, 1, &emitter));
141   ASSERT_EQ(1U, cc->GetNumEntries());
142 
143   int64 only_metahandle = cc->metahandles_[0];
144   EXPECT_TRUE(only_metahandle == pref1 || only_metahandle == pref2);
145 
146   cc->CleanUp();
147 }
148 
149 // Sanity check for building commits from DirectoryCommitContributions.
150 // This test makes two CommitContribution objects of different types and uses
151 // them to initialize a commit message.  Then it checks that the contents of the
152 // commit message match those of the directory they came from.
TEST_F(DirectoryCommitContributionTest,PrepareCommit)153 TEST_F(DirectoryCommitContributionTest, PrepareCommit) {
154   {
155     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
156     CreateUnsyncedItem(&trans, PREFERENCES, "pref1");
157     CreateUnsyncedItem(&trans, PREFERENCES, "pref2");
158     CreateUnsyncedItem(&trans, EXTENSIONS, "extension1");
159   }
160 
161   DirectoryTypeDebugInfoEmitter emitter1(PREFERENCES, &type_observers_);
162   DirectoryTypeDebugInfoEmitter emitter2(EXTENSIONS, &type_observers_);
163   scoped_ptr<DirectoryCommitContribution> pref_cc(
164       DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter1));
165   scoped_ptr<DirectoryCommitContribution> ext_cc(
166       DirectoryCommitContribution::Build(dir(), EXTENSIONS, 25, &emitter2));
167 
168   sync_pb::ClientToServerMessage message;
169   pref_cc->AddToCommitMessage(&message);
170   ext_cc->AddToCommitMessage(&message);
171 
172   const sync_pb::CommitMessage& commit_message = message.commit();
173 
174   std::set<syncable::Id> ids_for_commit;
175   ASSERT_EQ(3, commit_message.entries_size());
176   for (int i = 0; i < commit_message.entries_size(); ++i) {
177     const sync_pb::SyncEntity& entity = commit_message.entries(i);
178     // The entities in this test have client-style IDs since they've never been
179     // committed before, so we must use CreateFromClientString to re-create them
180     // from the commit message.
181     ids_for_commit.insert(syncable::Id::CreateFromClientString(
182             entity.id_string()));
183   }
184 
185   ASSERT_EQ(3U, ids_for_commit.size());
186   {
187     syncable::ReadTransaction trans(FROM_HERE, dir());
188     for (std::set<syncable::Id>::iterator it = ids_for_commit.begin();
189          it != ids_for_commit.end(); ++it) {
190       SCOPED_TRACE(it->value());
191       syncable::Entry entry(&trans, syncable::GET_BY_ID, *it);
192       ASSERT_TRUE(entry.good());
193       EXPECT_TRUE(entry.GetSyncing());
194     }
195   }
196 
197   pref_cc->CleanUp();
198   ext_cc->CleanUp();
199 }
200 
201 // Check that deletion requests include a model type.
202 // This was not always the case, but was implemented to allow us to loosen some
203 // other restrictions in the protocol.
TEST_F(DirectoryCommitContributionTest,DeletedItemsWithSpecifics)204 TEST_F(DirectoryCommitContributionTest, DeletedItemsWithSpecifics) {
205   int64 pref1;
206   {
207     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
208     pref1 = CreateSyncedItem(&trans, PREFERENCES, "pref1");
209     syncable::MutableEntry e1(&trans, syncable::GET_BY_HANDLE, pref1);
210     e1.PutIsDel(true);
211     e1.PutIsUnsynced(true);
212   }
213 
214   DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_);
215   scoped_ptr<DirectoryCommitContribution> pref_cc(
216       DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter));
217   ASSERT_TRUE(pref_cc);
218 
219   sync_pb::ClientToServerMessage message;
220   pref_cc->AddToCommitMessage(&message);
221 
222   const sync_pb::CommitMessage& commit_message = message.commit();
223   ASSERT_EQ(1, commit_message.entries_size());
224   EXPECT_TRUE(
225       commit_message.entries(0).specifics().has_preference());
226 
227   pref_cc->CleanUp();
228 }
229 
230 // As ususal, bookmarks are special.  Bookmark deletion is special.
231 // Deleted bookmarks include a valid "is folder" bit and their full specifics
232 // (especially the meta info, which is what server really wants).
TEST_F(DirectoryCommitContributionTest,DeletedBookmarksWithSpecifics)233 TEST_F(DirectoryCommitContributionTest, DeletedBookmarksWithSpecifics) {
234   int64 bm1;
235   {
236     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
237     bm1 = CreateSyncedItem(&trans, BOOKMARKS, "bm1");
238     syncable::MutableEntry e1(&trans, syncable::GET_BY_HANDLE, bm1);
239 
240     e1.PutIsDir(true);
241     e1.PutServerIsDir(true);
242 
243     sync_pb::EntitySpecifics specifics;
244     sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark();
245     bm_specifics->set_url("http://www.chrome.com");
246     bm_specifics->set_title("Chrome");
247     sync_pb::MetaInfo* meta_info = bm_specifics->add_meta_info();
248     meta_info->set_key("K");
249     meta_info->set_value("V");
250     e1.PutSpecifics(specifics);
251 
252     e1.PutIsDel(true);
253     e1.PutIsUnsynced(true);
254   }
255 
256   DirectoryTypeDebugInfoEmitter emitter(BOOKMARKS, &type_observers_);
257   scoped_ptr<DirectoryCommitContribution> bm_cc(
258       DirectoryCommitContribution::Build(dir(), BOOKMARKS, 25, &emitter));
259   ASSERT_TRUE(bm_cc);
260 
261   sync_pb::ClientToServerMessage message;
262   bm_cc->AddToCommitMessage(&message);
263 
264   const sync_pb::CommitMessage& commit_message = message.commit();
265   ASSERT_EQ(1, commit_message.entries_size());
266 
267   const sync_pb::SyncEntity& entity = commit_message.entries(0);
268   EXPECT_TRUE(entity.has_folder());
269   ASSERT_TRUE(entity.specifics().has_bookmark());
270   ASSERT_EQ(1, entity.specifics().bookmark().meta_info_size());
271   EXPECT_EQ("K", entity.specifics().bookmark().meta_info(0).key());
272   EXPECT_EQ("V", entity.specifics().bookmark().meta_info(0).value());
273 
274   bm_cc->CleanUp();
275 }
276 
277 // Creates some unsynced items, pretends to commit them, and hands back a
278 // specially crafted response to the syncer in order to test commit response
279 // processing.  The response simulates a succesful commit scenario.
TEST_F(DirectoryCommitContributionTest,ProcessCommitResponse)280 TEST_F(DirectoryCommitContributionTest, ProcessCommitResponse) {
281   int64 pref1_handle;
282   int64 pref2_handle;
283   int64 ext1_handle;
284   {
285     syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir());
286     pref1_handle = CreateUnsyncedItem(&trans, PREFERENCES, "pref1");
287     pref2_handle = CreateUnsyncedItem(&trans, PREFERENCES, "pref2");
288     ext1_handle = CreateUnsyncedItem(&trans, EXTENSIONS, "extension1");
289   }
290 
291   DirectoryTypeDebugInfoEmitter emitter1(PREFERENCES, &type_observers_);
292   DirectoryTypeDebugInfoEmitter emitter2(EXTENSIONS, &type_observers_);
293   scoped_ptr<DirectoryCommitContribution> pref_cc(
294       DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter1));
295   scoped_ptr<DirectoryCommitContribution> ext_cc(
296       DirectoryCommitContribution::Build(dir(), EXTENSIONS, 25, &emitter2));
297 
298   sync_pb::ClientToServerMessage message;
299   pref_cc->AddToCommitMessage(&message);
300   ext_cc->AddToCommitMessage(&message);
301 
302   const sync_pb::CommitMessage& commit_message = message.commit();
303   ASSERT_EQ(3, commit_message.entries_size());
304 
305   sync_pb::ClientToServerResponse response;
306   for (int i = 0; i < commit_message.entries_size(); ++i) {
307     sync_pb::SyncEntity entity = commit_message.entries(i);
308     sync_pb::CommitResponse_EntryResponse* entry_response =
309         response.mutable_commit()->add_entryresponse();
310     CreateSuccessfulCommitResponse(entity, entry_response);
311   }
312 
313   sessions::StatusController status;
314 
315   // Process these in reverse order.  Just because we can.
316   ext_cc->ProcessCommitResponse(response, &status);
317   pref_cc->ProcessCommitResponse(response, &status);
318 
319   {
320     syncable::ReadTransaction trans(FROM_HERE, dir());
321     syncable::Entry p1(&trans, syncable::GET_BY_HANDLE, pref1_handle);
322     EXPECT_TRUE(p1.GetId().ServerKnows());
323     EXPECT_FALSE(p1.GetSyncing());
324     EXPECT_LT(0, p1.GetServerVersion());
325 
326     syncable::Entry p2(&trans, syncable::GET_BY_HANDLE, pref2_handle);
327     EXPECT_TRUE(p2.GetId().ServerKnows());
328     EXPECT_FALSE(p2.GetSyncing());
329     EXPECT_LT(0, p2.GetServerVersion());
330 
331     syncable::Entry e1(&trans, syncable::GET_BY_HANDLE, ext1_handle);
332     EXPECT_TRUE(e1.GetId().ServerKnows());
333     EXPECT_FALSE(e1.GetSyncing());
334     EXPECT_LT(0, e1.GetServerVersion());
335   }
336 
337   pref_cc->CleanUp();
338   ext_cc->CleanUp();
339 }
340 
341 }  // namespace syncer
342