1 // Copyright 2013 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.
5 #include "chrome/browser/sync_file_system/drive_backend/conflict_resolver.h"
8 #include "base/callback.h"
9 #include "base/file_util.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/run_loop.h"
12 #include "chrome/browser/drive/drive_uploader.h"
13 #include "chrome/browser/drive/fake_drive_service.h"
14 #include "chrome/browser/sync_file_system/drive_backend/drive_backend_constants.h"
15 #include "chrome/browser/sync_file_system/drive_backend/drive_backend_test_util.h"
16 #include "chrome/browser/sync_file_system/drive_backend/fake_drive_service_helper.h"
17 #include "chrome/browser/sync_file_system/drive_backend/fake_drive_uploader.h"
18 #include "chrome/browser/sync_file_system/drive_backend/list_changes_task.h"
19 #include "chrome/browser/sync_file_system/drive_backend/local_to_remote_syncer.h"
20 #include "chrome/browser/sync_file_system/drive_backend/metadata_database.h"
21 #include "chrome/browser/sync_file_system/drive_backend/remote_to_local_syncer.h"
22 #include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h"
23 #include "chrome/browser/sync_file_system/drive_backend/sync_engine_initializer.h"
24 #include "chrome/browser/sync_file_system/fake_remote_change_processor.h"
25 #include "chrome/browser/sync_file_system/sync_file_system_test_util.h"
26 #include "chrome/browser/sync_file_system/syncable_file_system_util.h"
27 #include "content/public/test/test_browser_thread_bundle.h"
28 #include "google_apis/drive/gdata_errorcode.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 #include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
31 #include "third_party/leveldatabase/src/include/leveldb/env.h"
33 namespace sync_file_system {
34 namespace drive_backend {
38 fileapi::FileSystemURL URL(const GURL& origin,
39 const std::string& path) {
40 return CreateSyncableFileSystemURL(
41 origin, base::FilePath::FromUTF8Unsafe(path));
46 class ConflictResolverTest : public testing::Test,
47 public SyncEngineContext {
49 typedef FakeRemoteChangeProcessor::URLToFileChangesMap URLToFileChangesMap;
51 ConflictResolverTest()
52 : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
53 virtual ~ConflictResolverTest() {}
55 virtual void SetUp() OVERRIDE {
56 ASSERT_TRUE(database_dir_.CreateUniqueTempDir());
57 in_memory_env_.reset(leveldb::NewMemEnv(leveldb::Env::Default()));
59 fake_drive_service_.reset(new FakeDriveServiceWrapper);
60 ASSERT_TRUE(fake_drive_service_->LoadAccountMetadataForWapi(
61 "sync_file_system/account_metadata.json"));
62 ASSERT_TRUE(fake_drive_service_->LoadResourceListForWapi(
63 "gdata/empty_feed.json"));
65 drive_uploader_.reset(new FakeDriveUploader(fake_drive_service_.get()));
66 fake_drive_helper_.reset(new FakeDriveServiceHelper(
67 fake_drive_service_.get(), drive_uploader_.get(),
68 kSyncRootFolderTitle));
69 fake_remote_change_processor_.reset(new FakeRemoteChangeProcessor);
71 RegisterSyncableFileSystem();
74 virtual void TearDown() OVERRIDE {
75 RevokeSyncableFileSystem();
77 fake_remote_change_processor_.reset();
78 metadata_database_.reset();
79 fake_drive_helper_.reset();
80 drive_uploader_.reset();
81 fake_drive_service_.reset();
82 base::RunLoop().RunUntilIdle();
85 void InitializeMetadataDatabase() {
86 SyncEngineInitializer initializer(this,
87 base::MessageLoopProxy::current(),
88 fake_drive_service_.get(),
90 in_memory_env_.get());
91 SyncStatusCode status = SYNC_STATUS_UNKNOWN;
92 initializer.Run(CreateResultReceiver(&status));
93 base::RunLoop().RunUntilIdle();
94 EXPECT_EQ(SYNC_STATUS_OK, status);
95 metadata_database_ = initializer.PassMetadataDatabase();
98 void RegisterApp(const std::string& app_id,
99 const std::string& app_root_folder_id) {
100 SyncStatusCode status = SYNC_STATUS_FAILED;
101 metadata_database_->RegisterApp(app_id, app_root_folder_id,
102 CreateResultReceiver(&status));
103 base::RunLoop().RunUntilIdle();
104 EXPECT_EQ(SYNC_STATUS_OK, status);
107 virtual drive::DriveServiceInterface* GetDriveService() OVERRIDE {
108 return fake_drive_service_.get();
111 virtual drive::DriveUploaderInterface* GetDriveUploader() OVERRIDE {
112 return drive_uploader_.get();
115 virtual MetadataDatabase* GetMetadataDatabase() OVERRIDE {
116 return metadata_database_.get();
119 virtual RemoteChangeProcessor* GetRemoteChangeProcessor() OVERRIDE {
120 return fake_remote_change_processor_.get();
123 virtual base::SequencedTaskRunner* GetBlockingTaskRunner() OVERRIDE {
124 return base::MessageLoopProxy::current().get();
128 std::string CreateSyncRoot() {
129 std::string sync_root_folder_id;
130 EXPECT_EQ(google_apis::HTTP_CREATED,
131 fake_drive_helper_->AddOrphanedFolder(
132 kSyncRootFolderTitle, &sync_root_folder_id));
133 return sync_root_folder_id;
136 std::string CreateRemoteFolder(const std::string& parent_folder_id,
137 const std::string& title) {
138 std::string folder_id;
139 EXPECT_EQ(google_apis::HTTP_CREATED,
140 fake_drive_helper_->AddFolder(
141 parent_folder_id, title, &folder_id));
145 std::string CreateRemoteFile(const std::string& parent_folder_id,
146 const std::string& title,
147 const std::string& content) {
149 EXPECT_EQ(google_apis::HTTP_SUCCESS,
150 fake_drive_helper_->AddFile(
151 parent_folder_id, title, content, &file_id));
155 void CreateLocalFile(const fileapi::FileSystemURL& url) {
156 fake_remote_change_processor_->UpdateLocalFileMetadata(
157 url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
158 SYNC_FILE_TYPE_FILE));
161 google_apis::GDataErrorCode AddFileToFolder(
162 const std::string& parent_folder_id,
163 const std::string& file_id) {
164 google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
165 fake_drive_service_->AddResourceToDirectory(
166 parent_folder_id, file_id,
167 CreateResultReceiver(&error));
168 base::RunLoop().RunUntilIdle();
172 int CountParents(const std::string& file_id) {
173 scoped_ptr<google_apis::ResourceEntry> entry;
174 EXPECT_EQ(google_apis::HTTP_SUCCESS,
175 fake_drive_helper_->GetResourceEntry(file_id, &entry));
177 const ScopedVector<google_apis::Link>& links = entry->links();
178 for (ScopedVector<google_apis::Link>::const_iterator itr = links.begin();
179 itr != links.end(); ++itr) {
180 if ((*itr)->type() == google_apis::Link::LINK_PARENT)
186 SyncStatusCode RunRemoteToLocalSyncer() {
187 SyncStatusCode status = SYNC_STATUS_UNKNOWN;
188 scoped_ptr<RemoteToLocalSyncer> syncer(new RemoteToLocalSyncer(this));
189 syncer->Run(CreateResultReceiver(&status));
190 base::RunLoop().RunUntilIdle();
194 SyncStatusCode RunLocalToRemoteSyncer(
195 const fileapi::FileSystemURL& url,
196 const FileChange& file_change) {
197 SyncStatusCode status = SYNC_STATUS_UNKNOWN;
198 base::FilePath local_path = base::FilePath(FILE_PATH_LITERAL("dummy"));
199 if (file_change.IsAddOrUpdate())
200 CreateTemporaryFileInDir(database_dir_.path(), &local_path);
201 scoped_ptr<LocalToRemoteSyncer> syncer(new LocalToRemoteSyncer(
202 this, SyncFileMetadata(file_change.file_type(), 0, base::Time()),
203 file_change, local_path, url));
204 syncer->Run(CreateResultReceiver(&status));
205 base::RunLoop().RunUntilIdle();
206 if (status == SYNC_STATUS_OK)
207 fake_remote_change_processor_->ClearLocalChanges(url);
211 void RunRemoteToLocalSyncerUntilIdle() {
212 SyncStatusCode status = SYNC_STATUS_UNKNOWN;
213 while (status != SYNC_STATUS_NO_CHANGE_TO_SYNC)
214 status = RunRemoteToLocalSyncer();
217 SyncStatusCode RunConflictResolver() {
218 SyncStatusCode status = SYNC_STATUS_UNKNOWN;
219 ConflictResolver resolver(this);
220 resolver.Run(CreateResultReceiver(&status));
221 base::RunLoop().RunUntilIdle();
225 SyncStatusCode ListChanges() {
226 ListChangesTask list_changes(this);
227 SyncStatusCode status = SYNC_STATUS_UNKNOWN;
228 list_changes.Run(CreateResultReceiver(&status));
229 base::RunLoop().RunUntilIdle();
233 ScopedVector<google_apis::ResourceEntry>
234 GetResourceEntriesForParentAndTitle(const std::string& parent_folder_id,
235 const std::string& title) {
236 ScopedVector<google_apis::ResourceEntry> entries;
237 EXPECT_EQ(google_apis::HTTP_SUCCESS,
238 fake_drive_helper_->SearchByTitle(
239 parent_folder_id, title, &entries));
240 return entries.Pass();
243 void VerifyConflictResolution(const std::string& parent_folder_id,
244 const std::string& title,
245 const std::string& primary_file_id,
246 google_apis::DriveEntryKind kind) {
247 ScopedVector<google_apis::ResourceEntry> entries;
248 EXPECT_EQ(google_apis::HTTP_SUCCESS,
249 fake_drive_helper_->SearchByTitle(
250 parent_folder_id, title, &entries));
251 ASSERT_EQ(1u, entries.size());
252 EXPECT_EQ(primary_file_id, entries[0]->resource_id());
253 EXPECT_EQ(kind, entries[0]->kind());
256 void VerifyLocalChangeConsistency(
257 const URLToFileChangesMap& expected_changes) {
258 fake_remote_change_processor_->VerifyConsistency(expected_changes);
262 content::TestBrowserThreadBundle thread_bundle_;
263 base::ScopedTempDir database_dir_;
264 scoped_ptr<leveldb::Env> in_memory_env_;
266 scoped_ptr<FakeDriveServiceWrapper> fake_drive_service_;
267 scoped_ptr<FakeDriveUploader> drive_uploader_;
268 scoped_ptr<FakeDriveServiceHelper> fake_drive_helper_;
269 scoped_ptr<MetadataDatabase> metadata_database_;
270 scoped_ptr<FakeRemoteChangeProcessor> fake_remote_change_processor_;
272 DISALLOW_COPY_AND_ASSIGN(ConflictResolverTest);
275 TEST_F(ConflictResolverTest, NoFileToBeResolved) {
276 const GURL kOrigin("chrome-extension://example");
277 const std::string sync_root = CreateSyncRoot();
278 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
279 InitializeMetadataDatabase();
280 RegisterApp(kOrigin.host(), app_root);
281 RunRemoteToLocalSyncerUntilIdle();
283 EXPECT_EQ(SYNC_STATUS_NO_CONFLICT, RunConflictResolver());
286 TEST_F(ConflictResolverTest, ResolveConflict_Files) {
287 const GURL kOrigin("chrome-extension://example");
288 const std::string sync_root = CreateSyncRoot();
289 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
290 InitializeMetadataDatabase();
291 RegisterApp(kOrigin.host(), app_root);
292 RunRemoteToLocalSyncerUntilIdle();
294 const std::string kTitle = "foo";
295 const std::string primary = CreateRemoteFile(app_root, kTitle, "data1");
296 CreateRemoteFile(app_root, kTitle, "data2");
297 CreateRemoteFile(app_root, kTitle, "data3");
298 CreateRemoteFile(app_root, kTitle, "data4");
299 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
300 RunRemoteToLocalSyncerUntilIdle();
302 ScopedVector<google_apis::ResourceEntry> entries =
303 GetResourceEntriesForParentAndTitle(app_root, kTitle);
304 ASSERT_EQ(4u, entries.size());
306 // Only primary file should survive.
307 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
308 VerifyConflictResolution(app_root, kTitle, primary,
309 google_apis::ENTRY_KIND_FILE);
312 TEST_F(ConflictResolverTest, ResolveConflict_Folders) {
313 const GURL kOrigin("chrome-extension://example");
314 const std::string sync_root = CreateSyncRoot();
315 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
316 InitializeMetadataDatabase();
317 RegisterApp(kOrigin.host(), app_root);
318 RunRemoteToLocalSyncerUntilIdle();
320 const std::string kTitle = "foo";
321 const std::string primary = CreateRemoteFolder(app_root, kTitle);
322 CreateRemoteFolder(app_root, kTitle);
323 CreateRemoteFolder(app_root, kTitle);
324 CreateRemoteFolder(app_root, kTitle);
325 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
326 RunRemoteToLocalSyncerUntilIdle();
328 ScopedVector<google_apis::ResourceEntry> entries =
329 GetResourceEntriesForParentAndTitle(app_root, kTitle);
330 ASSERT_EQ(4u, entries.size());
332 // Only primary file should survive.
333 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
334 VerifyConflictResolution(app_root, kTitle, primary,
335 google_apis::ENTRY_KIND_FOLDER);
338 TEST_F(ConflictResolverTest, ResolveConflict_FilesAndFolders) {
339 const GURL kOrigin("chrome-extension://example");
340 const std::string sync_root = CreateSyncRoot();
341 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
342 InitializeMetadataDatabase();
343 RegisterApp(kOrigin.host(), app_root);
344 RunRemoteToLocalSyncerUntilIdle();
346 const std::string kTitle = "foo";
347 CreateRemoteFile(app_root, kTitle, "data");
348 const std::string primary = CreateRemoteFolder(app_root, kTitle);
349 CreateRemoteFile(app_root, kTitle, "data2");
350 CreateRemoteFolder(app_root, kTitle);
351 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
352 RunRemoteToLocalSyncerUntilIdle();
354 ScopedVector<google_apis::ResourceEntry> entries =
355 GetResourceEntriesForParentAndTitle(app_root, kTitle);
356 ASSERT_EQ(4u, entries.size());
358 // Only primary file should survive.
359 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
360 VerifyConflictResolution(app_root, kTitle, primary,
361 google_apis::ENTRY_KIND_FOLDER);
364 TEST_F(ConflictResolverTest, ResolveConflict_RemoteFolderOnLocalFile) {
365 const GURL kOrigin("chrome-extension://example");
366 const std::string sync_root = CreateSyncRoot();
367 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
368 InitializeMetadataDatabase();
369 RegisterApp(kOrigin.host(), app_root);
370 RunRemoteToLocalSyncerUntilIdle();
372 const std::string kTitle = "foo";
373 fileapi::FileSystemURL kURL = URL(kOrigin, kTitle);
375 // Create a file on local and sync it.
376 CreateLocalFile(kURL);
377 RunLocalToRemoteSyncer(
379 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE));
381 // Create a folder on remote and sync it.
382 const std::string primary = CreateRemoteFolder(app_root, kTitle);
383 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
384 RunRemoteToLocalSyncerUntilIdle();
386 ScopedVector<google_apis::ResourceEntry> entries =
387 GetResourceEntriesForParentAndTitle(app_root, kTitle);
388 ASSERT_EQ(2u, entries.size());
390 // Run conflict resolver. Only primary file should survive.
391 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
392 VerifyConflictResolution(app_root, kTitle, primary,
393 google_apis::ENTRY_KIND_FOLDER);
395 // Continue to run remote-to-local sync.
396 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
397 RunRemoteToLocalSyncerUntilIdle();
399 // Verify that the local side has been synced to the same state
400 // (i.e. file deletion and folder creation).
401 URLToFileChangesMap expected_changes;
402 expected_changes[kURL].push_back(
403 FileChange(FileChange::FILE_CHANGE_DELETE,
404 SYNC_FILE_TYPE_UNKNOWN));
405 expected_changes[kURL].push_back(
406 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
407 SYNC_FILE_TYPE_DIRECTORY));
408 VerifyLocalChangeConsistency(expected_changes);
411 TEST_F(ConflictResolverTest, ResolveConflict_RemoteNestedFolderOnLocalFile) {
412 const GURL kOrigin("chrome-extension://example");
413 const std::string sync_root = CreateSyncRoot();
414 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
415 InitializeMetadataDatabase();
416 RegisterApp(kOrigin.host(), app_root);
417 RunRemoteToLocalSyncerUntilIdle();
419 const std::string kTitle = "foo";
420 fileapi::FileSystemURL kURL = URL(kOrigin, kTitle);
422 // Create a file on local and sync it.
423 CreateLocalFile(kURL);
424 RunLocalToRemoteSyncer(
426 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE));
428 // Create a folder and subfolder in it on remote, and sync it.
429 const std::string primary = CreateRemoteFolder(app_root, kTitle);
430 CreateRemoteFolder(primary, "nested");
431 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
432 RunRemoteToLocalSyncerUntilIdle();
434 ScopedVector<google_apis::ResourceEntry> entries =
435 GetResourceEntriesForParentAndTitle(app_root, kTitle);
436 ASSERT_EQ(2u, entries.size());
438 // Run conflict resolver. Only primary file should survive.
439 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
440 VerifyConflictResolution(app_root, kTitle, primary,
441 google_apis::ENTRY_KIND_FOLDER);
443 // Continue to run remote-to-local sync.
444 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
445 RunRemoteToLocalSyncerUntilIdle();
447 // Verify that the local side has been synced to the same state
448 // (i.e. file deletion and folders creation).
449 URLToFileChangesMap expected_changes;
450 expected_changes[kURL].push_back(
451 FileChange(FileChange::FILE_CHANGE_DELETE,
452 SYNC_FILE_TYPE_UNKNOWN));
453 expected_changes[kURL].push_back(
454 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
455 SYNC_FILE_TYPE_DIRECTORY));
456 expected_changes[URL(kOrigin, "foo/nested")].push_back(
457 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
458 SYNC_FILE_TYPE_DIRECTORY));
459 VerifyLocalChangeConsistency(expected_changes);
462 TEST_F(ConflictResolverTest, ResolveMultiParents_File) {
463 const GURL kOrigin("chrome-extension://example");
464 const std::string sync_root = CreateSyncRoot();
465 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
466 InitializeMetadataDatabase();
467 RegisterApp(kOrigin.host(), app_root);
468 RunRemoteToLocalSyncerUntilIdle();
470 const std::string primary = CreateRemoteFolder(app_root, "primary");
471 const std::string file = CreateRemoteFile(primary, "file", "data");
472 ASSERT_EQ(google_apis::HTTP_SUCCESS,
473 AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary1"), file));
474 ASSERT_EQ(google_apis::HTTP_SUCCESS,
475 AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary2"), file));
476 ASSERT_EQ(google_apis::HTTP_SUCCESS,
477 AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary3"), file));
479 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
480 RunRemoteToLocalSyncerUntilIdle();
482 EXPECT_EQ(4, CountParents(file));
484 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
486 EXPECT_EQ(1, CountParents(file));
489 TEST_F(ConflictResolverTest, ResolveMultiParents_Folder) {
490 const GURL kOrigin("chrome-extension://example");
491 const std::string sync_root = CreateSyncRoot();
492 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
493 InitializeMetadataDatabase();
494 RegisterApp(kOrigin.host(), app_root);
495 RunRemoteToLocalSyncerUntilIdle();
497 const std::string primary = CreateRemoteFolder(app_root, "primary");
498 const std::string file = CreateRemoteFolder(primary, "folder");
499 ASSERT_EQ(google_apis::HTTP_SUCCESS,
500 AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary1"), file));
501 ASSERT_EQ(google_apis::HTTP_SUCCESS,
502 AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary2"), file));
503 ASSERT_EQ(google_apis::HTTP_SUCCESS,
504 AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary3"), file));
506 EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
507 RunRemoteToLocalSyncerUntilIdle();
509 EXPECT_EQ(4, CountParents(file));
511 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
513 EXPECT_EQ(1, CountParents(file));
516 } // namespace drive_backend
517 } // namespace sync_file_system