Home | History | Annotate | Download | only in drive_backend
      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 
     36 namespace sync_file_system {
     37 namespace drive_backend {
     38 
     39 namespace {
     40 
     41 fileapi::FileSystemURL URL(const GURL& origin,
     42                            const std::string& path) {
     43   return CreateSyncableFileSystemURL(
     44       origin, base::FilePath::FromUTF8Unsafe(path));
     45 }
     46 
     47 }  // namespace
     48 
     49 class 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 
    278 TEST_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 
    289 TEST_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 
    315 TEST_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 
    341 TEST_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 
    367 TEST_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 
    414 TEST_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 
    465 TEST_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 
    492 TEST_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