conflict_resolver_unittest.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
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.
4
5#include "chrome/browser/sync_file_system/drive_backend/conflict_resolver.h"
6
7#include "base/bind.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/drive_backend/sync_task_manager.h"
25#include "chrome/browser/sync_file_system/drive_backend/sync_task_token.h"
26#include "chrome/browser/sync_file_system/fake_remote_change_processor.h"
27#include "chrome/browser/sync_file_system/sync_file_system_test_util.h"
28#include "chrome/browser/sync_file_system/syncable_file_system_util.h"
29#include "content/public/test/test_browser_thread_bundle.h"
30#include "google_apis/drive/drive_api_parser.h"
31#include "google_apis/drive/gdata_errorcode.h"
32#include "testing/gtest/include/gtest/gtest.h"
33#include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
34#include "third_party/leveldatabase/src/include/leveldb/env.h"
35
36namespace sync_file_system {
37namespace drive_backend {
38
39namespace {
40
41fileapi::FileSystemURL URL(const GURL& origin,
42                           const std::string& path) {
43  return CreateSyncableFileSystemURL(
44      origin, base::FilePath::FromUTF8Unsafe(path));
45}
46
47}  // namespace
48
49class ConflictResolverTest : public testing::Test {
50 public:
51  typedef FakeRemoteChangeProcessor::URLToFileChangesMap URLToFileChangesMap;
52
53  ConflictResolverTest()
54      : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
55  virtual ~ConflictResolverTest() {}
56
57  virtual void SetUp() OVERRIDE {
58    ASSERT_TRUE(database_dir_.CreateUniqueTempDir());
59    in_memory_env_.reset(leveldb::NewMemEnv(leveldb::Env::Default()));
60
61    scoped_ptr<FakeDriveServiceWrapper>
62        fake_drive_service(new FakeDriveServiceWrapper);
63    scoped_ptr<drive::DriveUploaderInterface>
64        drive_uploader(new FakeDriveUploader(fake_drive_service.get()));
65    fake_drive_helper_.reset(
66        new FakeDriveServiceHelper(fake_drive_service.get(),
67                                   drive_uploader.get(),
68                                   kSyncRootFolderTitle));
69    remote_change_processor_.reset(new FakeRemoteChangeProcessor);
70
71    context_.reset(new SyncEngineContext(
72        fake_drive_service.PassAs<drive::DriveServiceInterface>(),
73        drive_uploader.Pass(),
74        NULL,
75        base::MessageLoopProxy::current(),
76        base::MessageLoopProxy::current(),
77        base::MessageLoopProxy::current()));
78    context_->SetRemoteChangeProcessor(remote_change_processor_.get());
79
80    RegisterSyncableFileSystem();
81
82    sync_task_manager_.reset(new SyncTaskManager(
83        base::WeakPtr<SyncTaskManager::Client>(),
84        10 /* maximum_background_task */,
85        base::MessageLoopProxy::current()));
86    sync_task_manager_->Initialize(SYNC_STATUS_OK);
87  }
88
89  virtual void TearDown() OVERRIDE {
90    sync_task_manager_.reset();
91    RevokeSyncableFileSystem();
92    fake_drive_helper_.reset();
93    context_.reset();
94    base::RunLoop().RunUntilIdle();
95  }
96
97  void InitializeMetadataDatabase() {
98    SyncEngineInitializer* initializer =
99        new SyncEngineInitializer(context_.get(),
100                                  database_dir_.path(),
101                                  in_memory_env_.get());
102    SyncStatusCode status = SYNC_STATUS_UNKNOWN;
103    sync_task_manager_->ScheduleSyncTask(
104        FROM_HERE,
105        scoped_ptr<SyncTask>(initializer),
106        SyncTaskManager::PRIORITY_MED,
107        base::Bind(&ConflictResolverTest::DidInitializeMetadataDatabase,
108                   base::Unretained(this), initializer, &status));
109
110    base::RunLoop().RunUntilIdle();
111    EXPECT_EQ(SYNC_STATUS_OK, status);
112  }
113
114  void DidInitializeMetadataDatabase(SyncEngineInitializer* initializer,
115                                     SyncStatusCode* status_out,
116                                     SyncStatusCode status) {
117    context_->SetMetadataDatabase(initializer->PassMetadataDatabase());
118    *status_out = status;
119  }
120
121  void RegisterApp(const std::string& app_id,
122                   const std::string& app_root_folder_id) {
123    SyncStatusCode status = SYNC_STATUS_FAILED;
124    context_->GetMetadataDatabase()->RegisterApp(app_id, app_root_folder_id,
125                                                 CreateResultReceiver(&status));
126    base::RunLoop().RunUntilIdle();
127    EXPECT_EQ(SYNC_STATUS_OK, status);
128  }
129
130 protected:
131  std::string CreateSyncRoot() {
132    std::string sync_root_folder_id;
133    EXPECT_EQ(google_apis::HTTP_CREATED,
134              fake_drive_helper_->AddOrphanedFolder(
135                  kSyncRootFolderTitle, &sync_root_folder_id));
136    return sync_root_folder_id;
137  }
138
139  std::string CreateRemoteFolder(const std::string& parent_folder_id,
140                                 const std::string& title) {
141    std::string folder_id;
142    EXPECT_EQ(google_apis::HTTP_CREATED,
143              fake_drive_helper_->AddFolder(
144                  parent_folder_id, title, &folder_id));
145    return folder_id;
146  }
147
148  std::string CreateRemoteFile(const std::string& parent_folder_id,
149                               const std::string& title,
150                               const std::string& content) {
151    std::string file_id;
152    EXPECT_EQ(google_apis::HTTP_SUCCESS,
153              fake_drive_helper_->AddFile(
154                  parent_folder_id, title, content, &file_id));
155    return file_id;
156  }
157
158  void CreateLocalFile(const fileapi::FileSystemURL& url) {
159    remote_change_processor_->UpdateLocalFileMetadata(
160        url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
161                        SYNC_FILE_TYPE_FILE));
162  }
163
164  google_apis::GDataErrorCode AddFileToFolder(
165      const std::string& parent_folder_id,
166      const std::string& file_id) {
167    google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
168    context_->GetDriveService()->AddResourceToDirectory(
169        parent_folder_id, file_id,
170        CreateResultReceiver(&error));
171    base::RunLoop().RunUntilIdle();
172    return error;
173  }
174
175  int CountParents(const std::string& file_id) {
176    scoped_ptr<google_apis::FileResource> entry;
177    EXPECT_EQ(google_apis::HTTP_SUCCESS,
178              fake_drive_helper_->GetFileResource(file_id, &entry));
179    return entry->parents().size();
180  }
181
182  SyncStatusCode RunRemoteToLocalSyncer() {
183    SyncStatusCode status = SYNC_STATUS_UNKNOWN;
184    scoped_ptr<RemoteToLocalSyncer> syncer(
185        new RemoteToLocalSyncer(context_.get()));
186    syncer->RunExclusive(CreateResultReceiver(&status));
187    base::RunLoop().RunUntilIdle();
188    return status;
189  }
190
191  SyncStatusCode RunLocalToRemoteSyncer(
192      const fileapi::FileSystemURL& url,
193      const FileChange& file_change) {
194    SyncStatusCode status = SYNC_STATUS_UNKNOWN;
195    base::FilePath local_path = base::FilePath(FILE_PATH_LITERAL("dummy"));
196    if (file_change.IsAddOrUpdate())
197      CreateTemporaryFileInDir(database_dir_.path(), &local_path);
198    scoped_ptr<LocalToRemoteSyncer> syncer(new LocalToRemoteSyncer(
199        context_.get(),
200        SyncFileMetadata(file_change.file_type(), 0, base::Time()),
201        file_change, local_path, url));
202    syncer->RunPreflight(SyncTaskToken::CreateForTesting(
203        CreateResultReceiver(&status)));
204    base::RunLoop().RunUntilIdle();
205    if (status == SYNC_STATUS_OK)
206      remote_change_processor_->ClearLocalChanges(url);
207    return status;
208  }
209
210  void RunRemoteToLocalSyncerUntilIdle() {
211    SyncStatusCode status = SYNC_STATUS_UNKNOWN;
212    while (status != SYNC_STATUS_NO_CHANGE_TO_SYNC)
213      status = RunRemoteToLocalSyncer();
214  }
215
216  SyncStatusCode RunConflictResolver() {
217    SyncStatusCode status = SYNC_STATUS_UNKNOWN;
218    ConflictResolver resolver(context_.get());
219    resolver.RunPreflight(SyncTaskToken::CreateForTesting(
220        CreateResultReceiver(&status)));
221    base::RunLoop().RunUntilIdle();
222    return status;
223  }
224
225  SyncStatusCode ListChanges() {
226    SyncStatusCode status = SYNC_STATUS_UNKNOWN;
227    sync_task_manager_->ScheduleSyncTask(
228        FROM_HERE,
229        scoped_ptr<SyncTask>(new ListChangesTask(context_.get())),
230        SyncTaskManager::PRIORITY_MED,
231        CreateResultReceiver(&status));
232    base::RunLoop().RunUntilIdle();
233    return status;
234  }
235
236  ScopedVector<google_apis::ResourceEntry>
237  GetResourceEntriesForParentAndTitle(const std::string& parent_folder_id,
238                                      const std::string& title) {
239    ScopedVector<google_apis::ResourceEntry> entries;
240    EXPECT_EQ(google_apis::HTTP_SUCCESS,
241              fake_drive_helper_->SearchByTitle(
242                  parent_folder_id, title, &entries));
243    return entries.Pass();
244  }
245
246  void VerifyConflictResolution(const std::string& parent_folder_id,
247                                const std::string& title,
248                                const std::string& primary_file_id,
249                                google_apis::DriveEntryKind kind) {
250    ScopedVector<google_apis::ResourceEntry> entries;
251    EXPECT_EQ(google_apis::HTTP_SUCCESS,
252              fake_drive_helper_->SearchByTitle(
253                  parent_folder_id, title, &entries));
254    ASSERT_EQ(1u, entries.size());
255    EXPECT_EQ(primary_file_id, entries[0]->resource_id());
256    EXPECT_EQ(kind, entries[0]->kind());
257  }
258
259  void VerifyLocalChangeConsistency(
260      const URLToFileChangesMap& expected_changes) {
261    remote_change_processor_->VerifyConsistency(expected_changes);
262  }
263
264 private:
265  content::TestBrowserThreadBundle thread_bundle_;
266  base::ScopedTempDir database_dir_;
267  scoped_ptr<leveldb::Env> in_memory_env_;
268
269  scoped_ptr<SyncEngineContext> context_;
270  scoped_ptr<FakeDriveServiceHelper> fake_drive_helper_;
271  scoped_ptr<FakeRemoteChangeProcessor> remote_change_processor_;
272
273  scoped_ptr<SyncTaskManager> sync_task_manager_;
274
275  DISALLOW_COPY_AND_ASSIGN(ConflictResolverTest);
276};
277
278TEST_F(ConflictResolverTest, NoFileToBeResolved) {
279  const GURL kOrigin("chrome-extension://example");
280  const std::string sync_root = CreateSyncRoot();
281  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
282  InitializeMetadataDatabase();
283  RegisterApp(kOrigin.host(), app_root);
284  RunRemoteToLocalSyncerUntilIdle();
285
286  EXPECT_EQ(SYNC_STATUS_NO_CONFLICT, RunConflictResolver());
287}
288
289TEST_F(ConflictResolverTest, ResolveConflict_Files) {
290  const GURL kOrigin("chrome-extension://example");
291  const std::string sync_root = CreateSyncRoot();
292  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
293  InitializeMetadataDatabase();
294  RegisterApp(kOrigin.host(), app_root);
295  RunRemoteToLocalSyncerUntilIdle();
296
297  const std::string kTitle = "foo";
298  const std::string primary = CreateRemoteFile(app_root, kTitle, "data1");
299  CreateRemoteFile(app_root, kTitle, "data2");
300  CreateRemoteFile(app_root, kTitle, "data3");
301  CreateRemoteFile(app_root, kTitle, "data4");
302  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
303  RunRemoteToLocalSyncerUntilIdle();
304
305  ScopedVector<google_apis::ResourceEntry> entries =
306      GetResourceEntriesForParentAndTitle(app_root, kTitle);
307  ASSERT_EQ(4u, entries.size());
308
309  // Only primary file should survive.
310  EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
311  VerifyConflictResolution(app_root, kTitle, primary,
312                           google_apis::ENTRY_KIND_FILE);
313}
314
315TEST_F(ConflictResolverTest, ResolveConflict_Folders) {
316  const GURL kOrigin("chrome-extension://example");
317  const std::string sync_root = CreateSyncRoot();
318  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
319  InitializeMetadataDatabase();
320  RegisterApp(kOrigin.host(), app_root);
321  RunRemoteToLocalSyncerUntilIdle();
322
323  const std::string kTitle = "foo";
324  const std::string primary = CreateRemoteFolder(app_root, kTitle);
325  CreateRemoteFolder(app_root, kTitle);
326  CreateRemoteFolder(app_root, kTitle);
327  CreateRemoteFolder(app_root, kTitle);
328  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
329  RunRemoteToLocalSyncerUntilIdle();
330
331  ScopedVector<google_apis::ResourceEntry> entries =
332      GetResourceEntriesForParentAndTitle(app_root, kTitle);
333  ASSERT_EQ(4u, entries.size());
334
335  // Only primary file should survive.
336  EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
337  VerifyConflictResolution(app_root, kTitle, primary,
338                           google_apis::ENTRY_KIND_FOLDER);
339}
340
341TEST_F(ConflictResolverTest, ResolveConflict_FilesAndFolders) {
342  const GURL kOrigin("chrome-extension://example");
343  const std::string sync_root = CreateSyncRoot();
344  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
345  InitializeMetadataDatabase();
346  RegisterApp(kOrigin.host(), app_root);
347  RunRemoteToLocalSyncerUntilIdle();
348
349  const std::string kTitle = "foo";
350  CreateRemoteFile(app_root, kTitle, "data");
351  const std::string primary = CreateRemoteFolder(app_root, kTitle);
352  CreateRemoteFile(app_root, kTitle, "data2");
353  CreateRemoteFolder(app_root, kTitle);
354  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
355  RunRemoteToLocalSyncerUntilIdle();
356
357  ScopedVector<google_apis::ResourceEntry> entries =
358      GetResourceEntriesForParentAndTitle(app_root, kTitle);
359  ASSERT_EQ(4u, entries.size());
360
361  // Only primary file should survive.
362  EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
363  VerifyConflictResolution(app_root, kTitle, primary,
364                           google_apis::ENTRY_KIND_FOLDER);
365}
366
367TEST_F(ConflictResolverTest, ResolveConflict_RemoteFolderOnLocalFile) {
368  const GURL kOrigin("chrome-extension://example");
369  const std::string sync_root = CreateSyncRoot();
370  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
371  InitializeMetadataDatabase();
372  RegisterApp(kOrigin.host(), app_root);
373  RunRemoteToLocalSyncerUntilIdle();
374
375  const std::string kTitle = "foo";
376  fileapi::FileSystemURL kURL = URL(kOrigin, kTitle);
377
378  // Create a file on local and sync it.
379  CreateLocalFile(kURL);
380  RunLocalToRemoteSyncer(
381      kURL,
382      FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE));
383
384  // Create a folder on remote and sync it.
385  const std::string primary = CreateRemoteFolder(app_root, kTitle);
386  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
387  RunRemoteToLocalSyncerUntilIdle();
388
389  ScopedVector<google_apis::ResourceEntry> entries =
390      GetResourceEntriesForParentAndTitle(app_root, kTitle);
391  ASSERT_EQ(2u, entries.size());
392
393  // Run conflict resolver. Only primary file should survive.
394  EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
395  VerifyConflictResolution(app_root, kTitle, primary,
396                           google_apis::ENTRY_KIND_FOLDER);
397
398  // Continue to run remote-to-local sync.
399  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
400  RunRemoteToLocalSyncerUntilIdle();
401
402  // Verify that the local side has been synced to the same state
403  // (i.e. file deletion and folder creation).
404  URLToFileChangesMap expected_changes;
405  expected_changes[kURL].push_back(
406      FileChange(FileChange::FILE_CHANGE_DELETE,
407                 SYNC_FILE_TYPE_UNKNOWN));
408  expected_changes[kURL].push_back(
409      FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
410                 SYNC_FILE_TYPE_DIRECTORY));
411  VerifyLocalChangeConsistency(expected_changes);
412}
413
414TEST_F(ConflictResolverTest, ResolveConflict_RemoteNestedFolderOnLocalFile) {
415  const GURL kOrigin("chrome-extension://example");
416  const std::string sync_root = CreateSyncRoot();
417  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
418  InitializeMetadataDatabase();
419  RegisterApp(kOrigin.host(), app_root);
420  RunRemoteToLocalSyncerUntilIdle();
421
422  const std::string kTitle = "foo";
423  fileapi::FileSystemURL kURL = URL(kOrigin, kTitle);
424
425  // Create a file on local and sync it.
426  CreateLocalFile(kURL);
427  RunLocalToRemoteSyncer(
428      kURL,
429      FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE));
430
431  // Create a folder and subfolder in it on remote, and sync it.
432  const std::string primary = CreateRemoteFolder(app_root, kTitle);
433  CreateRemoteFolder(primary, "nested");
434  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
435  RunRemoteToLocalSyncerUntilIdle();
436
437  ScopedVector<google_apis::ResourceEntry> entries =
438      GetResourceEntriesForParentAndTitle(app_root, kTitle);
439  ASSERT_EQ(2u, entries.size());
440
441  // Run conflict resolver. Only primary file should survive.
442  EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
443  VerifyConflictResolution(app_root, kTitle, primary,
444                           google_apis::ENTRY_KIND_FOLDER);
445
446  // Continue to run remote-to-local sync.
447  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
448  RunRemoteToLocalSyncerUntilIdle();
449
450  // Verify that the local side has been synced to the same state
451  // (i.e. file deletion and folders creation).
452  URLToFileChangesMap expected_changes;
453  expected_changes[kURL].push_back(
454      FileChange(FileChange::FILE_CHANGE_DELETE,
455                 SYNC_FILE_TYPE_UNKNOWN));
456  expected_changes[kURL].push_back(
457      FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
458                 SYNC_FILE_TYPE_DIRECTORY));
459  expected_changes[URL(kOrigin, "foo/nested")].push_back(
460      FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
461                 SYNC_FILE_TYPE_DIRECTORY));
462  VerifyLocalChangeConsistency(expected_changes);
463}
464
465TEST_F(ConflictResolverTest, ResolveMultiParents_File) {
466  const GURL kOrigin("chrome-extension://example");
467  const std::string sync_root = CreateSyncRoot();
468  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
469  InitializeMetadataDatabase();
470  RegisterApp(kOrigin.host(), app_root);
471  RunRemoteToLocalSyncerUntilIdle();
472
473  const std::string primary = CreateRemoteFolder(app_root, "primary");
474  const std::string file = CreateRemoteFile(primary, "file", "data");
475  ASSERT_EQ(google_apis::HTTP_SUCCESS,
476            AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary1"), file));
477  ASSERT_EQ(google_apis::HTTP_SUCCESS,
478            AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary2"), file));
479  ASSERT_EQ(google_apis::HTTP_SUCCESS,
480            AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary3"), file));
481
482  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
483  RunRemoteToLocalSyncerUntilIdle();
484
485  EXPECT_EQ(4, CountParents(file));
486
487  EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
488
489  EXPECT_EQ(1, CountParents(file));
490}
491
492TEST_F(ConflictResolverTest, ResolveMultiParents_Folder) {
493  const GURL kOrigin("chrome-extension://example");
494  const std::string sync_root = CreateSyncRoot();
495  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
496  InitializeMetadataDatabase();
497  RegisterApp(kOrigin.host(), app_root);
498  RunRemoteToLocalSyncerUntilIdle();
499
500  const std::string primary = CreateRemoteFolder(app_root, "primary");
501  const std::string file = CreateRemoteFolder(primary, "folder");
502  ASSERT_EQ(google_apis::HTTP_SUCCESS,
503            AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary1"), file));
504  ASSERT_EQ(google_apis::HTTP_SUCCESS,
505            AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary2"), file));
506  ASSERT_EQ(google_apis::HTTP_SUCCESS,
507            AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary3"), file));
508
509  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
510  RunRemoteToLocalSyncerUntilIdle();
511
512  EXPECT_EQ(4, CountParents(file));
513
514  EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
515
516  EXPECT_EQ(1, CountParents(file));
517}
518
519}  // namespace drive_backend
520}  // namespace sync_file_system
521