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/list_changes_task.h"
18 #include "chrome/browser/sync_file_system/drive_backend/local_to_remote_syncer.h"
19 #include "chrome/browser/sync_file_system/drive_backend/metadata_database.h"
20 #include "chrome/browser/sync_file_system/drive_backend/remote_to_local_syncer.h"
21 #include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h"
22 #include "chrome/browser/sync_file_system/drive_backend/sync_engine_initializer.h"
23 #include "chrome/browser/sync_file_system/drive_backend_v1/fake_drive_uploader.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"
31 namespace sync_file_system
{
32 namespace drive_backend
{
36 fileapi::FileSystemURL
URL(const GURL
& origin
,
37 const std::string
& path
) {
38 return CreateSyncableFileSystemURL(
39 origin
, base::FilePath::FromUTF8Unsafe(path
));
44 class ConflictResolverTest
: public testing::Test
,
45 public SyncEngineContext
{
47 typedef FakeRemoteChangeProcessor::URLToFileChangesMap URLToFileChangesMap
;
49 ConflictResolverTest()
50 : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP
) {}
51 virtual ~ConflictResolverTest() {}
53 virtual void SetUp() OVERRIDE
{
54 ASSERT_TRUE(database_dir_
.CreateUniqueTempDir());
56 fake_drive_service_
.reset(new FakeDriveServiceWrapper
);
57 ASSERT_TRUE(fake_drive_service_
->LoadAccountMetadataForWapi(
58 "sync_file_system/account_metadata.json"));
59 ASSERT_TRUE(fake_drive_service_
->LoadResourceListForWapi(
60 "gdata/empty_feed.json"));
62 drive_uploader_
.reset(new FakeDriveUploader(fake_drive_service_
.get()));
63 fake_drive_helper_
.reset(new FakeDriveServiceHelper(
64 fake_drive_service_
.get(), drive_uploader_
.get(),
65 kSyncRootFolderTitle
));
66 fake_remote_change_processor_
.reset(new FakeRemoteChangeProcessor
);
68 RegisterSyncableFileSystem();
71 virtual void TearDown() OVERRIDE
{
72 RevokeSyncableFileSystem();
74 fake_remote_change_processor_
.reset();
75 metadata_database_
.reset();
76 fake_drive_helper_
.reset();
77 drive_uploader_
.reset();
78 fake_drive_service_
.reset();
79 base::RunLoop().RunUntilIdle();
82 void InitializeMetadataDatabase() {
83 SyncEngineInitializer
initializer(this,
84 base::MessageLoopProxy::current(),
85 fake_drive_service_
.get(),
86 database_dir_
.path());
87 SyncStatusCode status
= SYNC_STATUS_UNKNOWN
;
88 initializer
.Run(CreateResultReceiver(&status
));
89 base::RunLoop().RunUntilIdle();
90 EXPECT_EQ(SYNC_STATUS_OK
, status
);
91 metadata_database_
= initializer
.PassMetadataDatabase();
94 void RegisterApp(const std::string
& app_id
,
95 const std::string
& app_root_folder_id
) {
96 SyncStatusCode status
= SYNC_STATUS_FAILED
;
97 metadata_database_
->RegisterApp(app_id
, app_root_folder_id
,
98 CreateResultReceiver(&status
));
99 base::RunLoop().RunUntilIdle();
100 EXPECT_EQ(SYNC_STATUS_OK
, status
);
103 virtual drive::DriveServiceInterface
* GetDriveService() OVERRIDE
{
104 return fake_drive_service_
.get();
107 virtual drive::DriveUploaderInterface
* GetDriveUploader() OVERRIDE
{
108 return drive_uploader_
.get();
111 virtual MetadataDatabase
* GetMetadataDatabase() OVERRIDE
{
112 return metadata_database_
.get();
115 virtual RemoteChangeProcessor
* GetRemoteChangeProcessor() OVERRIDE
{
116 return fake_remote_change_processor_
.get();
119 virtual base::SequencedTaskRunner
* GetBlockingTaskRunner() OVERRIDE
{
120 return base::MessageLoopProxy::current().get();
124 std::string
CreateSyncRoot() {
125 std::string sync_root_folder_id
;
126 EXPECT_EQ(google_apis::HTTP_CREATED
,
127 fake_drive_helper_
->AddOrphanedFolder(
128 kSyncRootFolderTitle
, &sync_root_folder_id
));
129 return sync_root_folder_id
;
132 std::string
CreateRemoteFolder(const std::string
& parent_folder_id
,
133 const std::string
& title
) {
134 std::string folder_id
;
135 EXPECT_EQ(google_apis::HTTP_CREATED
,
136 fake_drive_helper_
->AddFolder(
137 parent_folder_id
, title
, &folder_id
));
141 std::string
CreateRemoteFile(const std::string
& parent_folder_id
,
142 const std::string
& title
,
143 const std::string
& content
) {
145 EXPECT_EQ(google_apis::HTTP_SUCCESS
,
146 fake_drive_helper_
->AddFile(
147 parent_folder_id
, title
, content
, &file_id
));
151 void CreateLocalFile(const fileapi::FileSystemURL
& url
) {
152 fake_remote_change_processor_
->UpdateLocalFileMetadata(
153 url
, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE
,
154 SYNC_FILE_TYPE_FILE
));
157 google_apis::GDataErrorCode
AddFileToFolder(
158 const std::string
& parent_folder_id
,
159 const std::string
& file_id
) {
160 google_apis::GDataErrorCode error
= google_apis::GDATA_OTHER_ERROR
;
161 fake_drive_service_
->AddResourceToDirectory(
162 parent_folder_id
, file_id
,
163 CreateResultReceiver(&error
));
164 base::RunLoop().RunUntilIdle();
168 int CountParents(const std::string
& file_id
) {
169 scoped_ptr
<google_apis::ResourceEntry
> entry
;
170 EXPECT_EQ(google_apis::HTTP_SUCCESS
,
171 fake_drive_helper_
->GetResourceEntry(file_id
, &entry
));
173 const ScopedVector
<google_apis::Link
>& links
= entry
->links();
174 for (ScopedVector
<google_apis::Link
>::const_iterator itr
= links
.begin();
175 itr
!= links
.end(); ++itr
) {
176 if ((*itr
)->type() == google_apis::Link::LINK_PARENT
)
182 SyncStatusCode
RunRemoteToLocalSyncer() {
183 SyncStatusCode status
= SYNC_STATUS_UNKNOWN
;
184 scoped_ptr
<RemoteToLocalSyncer
> syncer(new RemoteToLocalSyncer(this));
185 syncer
->Run(CreateResultReceiver(&status
));
186 base::RunLoop().RunUntilIdle();
190 SyncStatusCode
RunLocalToRemoteSyncer(
191 const fileapi::FileSystemURL
& url
,
192 const FileChange
& file_change
) {
193 SyncStatusCode status
= SYNC_STATUS_UNKNOWN
;
194 base::FilePath local_path
= base::FilePath(FILE_PATH_LITERAL("dummy"));
195 if (file_change
.IsAddOrUpdate())
196 CreateTemporaryFileInDir(database_dir_
.path(), &local_path
);
197 scoped_ptr
<LocalToRemoteSyncer
> syncer(new LocalToRemoteSyncer(
198 this, SyncFileMetadata(file_change
.file_type(), 0, base::Time()),
199 file_change
, local_path
, url
));
200 syncer
->Run(CreateResultReceiver(&status
));
201 base::RunLoop().RunUntilIdle();
202 if (status
== SYNC_STATUS_OK
)
203 fake_remote_change_processor_
->ClearLocalChanges(url
);
207 void RunRemoteToLocalSyncerUntilIdle() {
208 SyncStatusCode status
= SYNC_STATUS_UNKNOWN
;
209 while (status
!= SYNC_STATUS_NO_CHANGE_TO_SYNC
)
210 status
= RunRemoteToLocalSyncer();
213 SyncStatusCode
RunConflictResolver() {
214 SyncStatusCode status
= SYNC_STATUS_UNKNOWN
;
215 ConflictResolver
resolver(this);
216 resolver
.Run(CreateResultReceiver(&status
));
217 base::RunLoop().RunUntilIdle();
221 SyncStatusCode
ListChanges() {
222 ListChangesTask
list_changes(this);
223 SyncStatusCode status
= SYNC_STATUS_UNKNOWN
;
224 list_changes
.Run(CreateResultReceiver(&status
));
225 base::RunLoop().RunUntilIdle();
229 ScopedVector
<google_apis::ResourceEntry
>
230 GetResourceEntriesForParentAndTitle(const std::string
& parent_folder_id
,
231 const std::string
& title
) {
232 ScopedVector
<google_apis::ResourceEntry
> entries
;
233 EXPECT_EQ(google_apis::HTTP_SUCCESS
,
234 fake_drive_helper_
->SearchByTitle(
235 parent_folder_id
, title
, &entries
));
236 return entries
.Pass();
239 void VerifyConflictResolution(const std::string
& parent_folder_id
,
240 const std::string
& title
,
241 const std::string
& primary_file_id
,
242 google_apis::DriveEntryKind kind
) {
243 ScopedVector
<google_apis::ResourceEntry
> entries
;
244 EXPECT_EQ(google_apis::HTTP_SUCCESS
,
245 fake_drive_helper_
->SearchByTitle(
246 parent_folder_id
, title
, &entries
));
247 ASSERT_EQ(1u, entries
.size());
248 EXPECT_EQ(primary_file_id
, entries
[0]->resource_id());
249 EXPECT_EQ(kind
, entries
[0]->kind());
252 void VerifyLocalChangeConsistency(
253 const URLToFileChangesMap
& expected_changes
) {
254 fake_remote_change_processor_
->VerifyConsistency(expected_changes
);
258 content::TestBrowserThreadBundle thread_bundle_
;
259 base::ScopedTempDir database_dir_
;
261 scoped_ptr
<FakeDriveServiceWrapper
> fake_drive_service_
;
262 scoped_ptr
<FakeDriveUploader
> drive_uploader_
;
263 scoped_ptr
<FakeDriveServiceHelper
> fake_drive_helper_
;
264 scoped_ptr
<MetadataDatabase
> metadata_database_
;
265 scoped_ptr
<FakeRemoteChangeProcessor
> fake_remote_change_processor_
;
267 DISALLOW_COPY_AND_ASSIGN(ConflictResolverTest
);
270 TEST_F(ConflictResolverTest
, NoFileToBeResolved
) {
271 const GURL
kOrigin("chrome-extension://example");
272 const std::string sync_root
= CreateSyncRoot();
273 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
274 InitializeMetadataDatabase();
275 RegisterApp(kOrigin
.host(), app_root
);
276 RunRemoteToLocalSyncerUntilIdle();
278 EXPECT_EQ(SYNC_STATUS_NO_CONFLICT
, RunConflictResolver());
281 TEST_F(ConflictResolverTest
, ResolveConflict_Files
) {
282 const GURL
kOrigin("chrome-extension://example");
283 const std::string sync_root
= CreateSyncRoot();
284 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
285 InitializeMetadataDatabase();
286 RegisterApp(kOrigin
.host(), app_root
);
287 RunRemoteToLocalSyncerUntilIdle();
289 const std::string kTitle
= "foo";
290 const std::string primary
= CreateRemoteFile(app_root
, kTitle
, "data1");
291 CreateRemoteFile(app_root
, kTitle
, "data2");
292 CreateRemoteFile(app_root
, kTitle
, "data3");
293 CreateRemoteFile(app_root
, kTitle
, "data4");
294 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
295 RunRemoteToLocalSyncerUntilIdle();
297 ScopedVector
<google_apis::ResourceEntry
> entries
=
298 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
299 ASSERT_EQ(4u, entries
.size());
301 // Only primary file should survive.
302 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
303 VerifyConflictResolution(app_root
, kTitle
, primary
,
304 google_apis::ENTRY_KIND_FILE
);
307 TEST_F(ConflictResolverTest
, ResolveConflict_Folders
) {
308 const GURL
kOrigin("chrome-extension://example");
309 const std::string sync_root
= CreateSyncRoot();
310 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
311 InitializeMetadataDatabase();
312 RegisterApp(kOrigin
.host(), app_root
);
313 RunRemoteToLocalSyncerUntilIdle();
315 const std::string kTitle
= "foo";
316 const std::string primary
= CreateRemoteFolder(app_root
, kTitle
);
317 CreateRemoteFolder(app_root
, kTitle
);
318 CreateRemoteFolder(app_root
, kTitle
);
319 CreateRemoteFolder(app_root
, kTitle
);
320 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
321 RunRemoteToLocalSyncerUntilIdle();
323 ScopedVector
<google_apis::ResourceEntry
> entries
=
324 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
325 ASSERT_EQ(4u, entries
.size());
327 // Only primary file should survive.
328 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
329 VerifyConflictResolution(app_root
, kTitle
, primary
,
330 google_apis::ENTRY_KIND_FOLDER
);
333 TEST_F(ConflictResolverTest
, ResolveConflict_FilesAndFolders
) {
334 const GURL
kOrigin("chrome-extension://example");
335 const std::string sync_root
= CreateSyncRoot();
336 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
337 InitializeMetadataDatabase();
338 RegisterApp(kOrigin
.host(), app_root
);
339 RunRemoteToLocalSyncerUntilIdle();
341 const std::string kTitle
= "foo";
342 CreateRemoteFile(app_root
, kTitle
, "data");
343 const std::string primary
= CreateRemoteFolder(app_root
, kTitle
);
344 CreateRemoteFile(app_root
, kTitle
, "data2");
345 CreateRemoteFolder(app_root
, kTitle
);
346 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
347 RunRemoteToLocalSyncerUntilIdle();
349 ScopedVector
<google_apis::ResourceEntry
> entries
=
350 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
351 ASSERT_EQ(4u, entries
.size());
353 // Only primary file should survive.
354 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
355 VerifyConflictResolution(app_root
, kTitle
, primary
,
356 google_apis::ENTRY_KIND_FOLDER
);
359 TEST_F(ConflictResolverTest
, ResolveConflict_RemoteFolderOnLocalFile
) {
360 const GURL
kOrigin("chrome-extension://example");
361 const std::string sync_root
= CreateSyncRoot();
362 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
363 InitializeMetadataDatabase();
364 RegisterApp(kOrigin
.host(), app_root
);
365 RunRemoteToLocalSyncerUntilIdle();
367 const std::string kTitle
= "foo";
368 fileapi::FileSystemURL kURL
= URL(kOrigin
, kTitle
);
370 // Create a file on local and sync it.
371 CreateLocalFile(kURL
);
372 RunLocalToRemoteSyncer(
374 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE
, SYNC_FILE_TYPE_FILE
));
376 // Create a folder on remote and sync it.
377 const std::string primary
= CreateRemoteFolder(app_root
, kTitle
);
378 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
379 RunRemoteToLocalSyncerUntilIdle();
381 ScopedVector
<google_apis::ResourceEntry
> entries
=
382 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
383 ASSERT_EQ(2u, entries
.size());
385 // Run conflict resolver. Only primary file should survive.
386 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
387 VerifyConflictResolution(app_root
, kTitle
, primary
,
388 google_apis::ENTRY_KIND_FOLDER
);
390 // Continue to run remote-to-local sync.
391 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
392 RunRemoteToLocalSyncerUntilIdle();
394 // Verify that the local side has been synced to the same state
395 // (i.e. file deletion and folder creation).
396 URLToFileChangesMap expected_changes
;
397 expected_changes
[kURL
].push_back(
398 FileChange(FileChange::FILE_CHANGE_DELETE
,
399 SYNC_FILE_TYPE_UNKNOWN
));
400 expected_changes
[kURL
].push_back(
401 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE
,
402 SYNC_FILE_TYPE_DIRECTORY
));
403 VerifyLocalChangeConsistency(expected_changes
);
406 TEST_F(ConflictResolverTest
, ResolveConflict_RemoteNestedFolderOnLocalFile
) {
407 const GURL
kOrigin("chrome-extension://example");
408 const std::string sync_root
= CreateSyncRoot();
409 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
410 InitializeMetadataDatabase();
411 RegisterApp(kOrigin
.host(), app_root
);
412 RunRemoteToLocalSyncerUntilIdle();
414 const std::string kTitle
= "foo";
415 fileapi::FileSystemURL kURL
= URL(kOrigin
, kTitle
);
417 // Create a file on local and sync it.
418 CreateLocalFile(kURL
);
419 RunLocalToRemoteSyncer(
421 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE
, SYNC_FILE_TYPE_FILE
));
423 // Create a folder and subfolder in it on remote, and sync it.
424 const std::string primary
= CreateRemoteFolder(app_root
, kTitle
);
425 CreateRemoteFolder(primary
, "nested");
426 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
427 RunRemoteToLocalSyncerUntilIdle();
429 ScopedVector
<google_apis::ResourceEntry
> entries
=
430 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
431 ASSERT_EQ(2u, entries
.size());
433 // Run conflict resolver. Only primary file should survive.
434 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
435 VerifyConflictResolution(app_root
, kTitle
, primary
,
436 google_apis::ENTRY_KIND_FOLDER
);
438 // Continue to run remote-to-local sync.
439 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
440 RunRemoteToLocalSyncerUntilIdle();
442 // Verify that the local side has been synced to the same state
443 // (i.e. file deletion and folders creation).
444 URLToFileChangesMap expected_changes
;
445 expected_changes
[kURL
].push_back(
446 FileChange(FileChange::FILE_CHANGE_DELETE
,
447 SYNC_FILE_TYPE_UNKNOWN
));
448 expected_changes
[kURL
].push_back(
449 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE
,
450 SYNC_FILE_TYPE_DIRECTORY
));
451 expected_changes
[URL(kOrigin
, "foo/nested")].push_back(
452 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE
,
453 SYNC_FILE_TYPE_DIRECTORY
));
454 VerifyLocalChangeConsistency(expected_changes
);
457 TEST_F(ConflictResolverTest
, ResolveMultiParents_File
) {
458 const GURL
kOrigin("chrome-extension://example");
459 const std::string sync_root
= CreateSyncRoot();
460 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
461 InitializeMetadataDatabase();
462 RegisterApp(kOrigin
.host(), app_root
);
463 RunRemoteToLocalSyncerUntilIdle();
465 const std::string primary
= CreateRemoteFolder(app_root
, "primary");
466 const std::string file
= CreateRemoteFile(primary
, "file", "data");
467 ASSERT_EQ(google_apis::HTTP_SUCCESS
,
468 AddFileToFolder(CreateRemoteFolder(app_root
, "nonprimary1"), file
));
469 ASSERT_EQ(google_apis::HTTP_SUCCESS
,
470 AddFileToFolder(CreateRemoteFolder(app_root
, "nonprimary2"), file
));
471 ASSERT_EQ(google_apis::HTTP_SUCCESS
,
472 AddFileToFolder(CreateRemoteFolder(app_root
, "nonprimary3"), file
));
474 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
475 RunRemoteToLocalSyncerUntilIdle();
477 EXPECT_EQ(4, CountParents(file
));
479 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
481 EXPECT_EQ(1, CountParents(file
));
484 TEST_F(ConflictResolverTest
, ResolveMultiParents_Folder
) {
485 const GURL
kOrigin("chrome-extension://example");
486 const std::string sync_root
= CreateSyncRoot();
487 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
488 InitializeMetadataDatabase();
489 RegisterApp(kOrigin
.host(), app_root
);
490 RunRemoteToLocalSyncerUntilIdle();
492 const std::string primary
= CreateRemoteFolder(app_root
, "primary");
493 const std::string file
= CreateRemoteFolder(primary
, "folder");
494 ASSERT_EQ(google_apis::HTTP_SUCCESS
,
495 AddFileToFolder(CreateRemoteFolder(app_root
, "nonprimary1"), file
));
496 ASSERT_EQ(google_apis::HTTP_SUCCESS
,
497 AddFileToFolder(CreateRemoteFolder(app_root
, "nonprimary2"), file
));
498 ASSERT_EQ(google_apis::HTTP_SUCCESS
,
499 AddFileToFolder(CreateRemoteFolder(app_root
, "nonprimary3"), file
));
501 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
502 RunRemoteToLocalSyncerUntilIdle();
504 EXPECT_EQ(4, CountParents(file
));
506 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
508 EXPECT_EQ(1, CountParents(file
));
511 } // namespace drive_backend
512 } // namespace sync_file_system