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