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/files/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 = context_->GetMetadataDatabase()->RegisterApp(
123        app_id, app_root_folder_id);
124    EXPECT_EQ(SYNC_STATUS_OK, status);
125  }
126
127 protected:
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;
134  }
135
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));
142    return folder_id;
143  }
144
145  std::string CreateRemoteFile(const std::string& parent_folder_id,
146                               const std::string& title,
147                               const std::string& content) {
148    std::string file_id;
149    EXPECT_EQ(google_apis::HTTP_SUCCESS,
150              fake_drive_helper_->AddFile(
151                  parent_folder_id, title, content, &file_id));
152    return file_id;
153  }
154
155  void CreateLocalFile(const storage::FileSystemURL& url) {
156    remote_change_processor_->UpdateLocalFileMetadata(
157        url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
158                        SYNC_FILE_TYPE_FILE));
159  }
160
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    context_->GetDriveService()->AddResourceToDirectory(
166        parent_folder_id, file_id,
167        CreateResultReceiver(&error));
168    base::RunLoop().RunUntilIdle();
169    return error;
170  }
171
172  int CountParents(const std::string& file_id) {
173    scoped_ptr<google_apis::FileResource> entry;
174    EXPECT_EQ(google_apis::HTTP_SUCCESS,
175              fake_drive_helper_->GetFileResource(file_id, &entry));
176    return entry->parents().size();
177  }
178
179  SyncStatusCode RunRemoteToLocalSyncer() {
180    SyncStatusCode status = SYNC_STATUS_UNKNOWN;
181    scoped_ptr<RemoteToLocalSyncer> syncer(
182        new RemoteToLocalSyncer(context_.get()));
183    syncer->RunPreflight(SyncTaskToken::CreateForTesting(
184        CreateResultReceiver(&status)));
185    base::RunLoop().RunUntilIdle();
186    return status;
187  }
188
189  SyncStatusCode RunLocalToRemoteSyncer(const storage::FileSystemURL& url,
190                                        const FileChange& file_change) {
191    SyncStatusCode status = SYNC_STATUS_UNKNOWN;
192    base::FilePath local_path = base::FilePath(FILE_PATH_LITERAL("dummy"));
193    if (file_change.IsAddOrUpdate())
194      CreateTemporaryFileInDir(database_dir_.path(), &local_path);
195    scoped_ptr<LocalToRemoteSyncer> syncer(new LocalToRemoteSyncer(
196        context_.get(),
197        SyncFileMetadata(file_change.file_type(), 0, base::Time()),
198        file_change, local_path, url));
199    syncer->RunPreflight(SyncTaskToken::CreateForTesting(
200        CreateResultReceiver(&status)));
201    base::RunLoop().RunUntilIdle();
202    if (status == SYNC_STATUS_OK)
203      remote_change_processor_->ClearLocalChanges(url);
204    return status;
205  }
206
207  void RunRemoteToLocalSyncerUntilIdle() {
208    const int kRetryLimit = 100;
209    SyncStatusCode status;
210    int retry_count = 0;
211    MetadataDatabase* metadata_database = context_->GetMetadataDatabase();
212    do {
213      if (retry_count++ > kRetryLimit)
214        break;
215      status = RunRemoteToLocalSyncer();
216    } while (status == SYNC_STATUS_OK ||
217             status == SYNC_STATUS_RETRY ||
218             metadata_database->PromoteDemotedTrackers());
219    EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, status);
220  }
221
222  SyncStatusCode RunConflictResolver() {
223    SyncStatusCode status = SYNC_STATUS_UNKNOWN;
224    ConflictResolver resolver(context_.get());
225    resolver.RunPreflight(SyncTaskToken::CreateForTesting(
226        CreateResultReceiver(&status)));
227    base::RunLoop().RunUntilIdle();
228    return status;
229  }
230
231  SyncStatusCode ListChanges() {
232    SyncStatusCode status = SYNC_STATUS_UNKNOWN;
233    sync_task_manager_->ScheduleSyncTask(
234        FROM_HERE,
235        scoped_ptr<SyncTask>(new ListChangesTask(context_.get())),
236        SyncTaskManager::PRIORITY_MED,
237        CreateResultReceiver(&status));
238    base::RunLoop().RunUntilIdle();
239    return status;
240  }
241
242  ScopedVector<google_apis::ResourceEntry>
243  GetResourceEntriesForParentAndTitle(const std::string& parent_folder_id,
244                                      const std::string& title) {
245    ScopedVector<google_apis::ResourceEntry> entries;
246    EXPECT_EQ(google_apis::HTTP_SUCCESS,
247              fake_drive_helper_->SearchByTitle(
248                  parent_folder_id, title, &entries));
249    return entries.Pass();
250  }
251
252  void VerifyConflictResolution(
253      const std::string& parent_folder_id,
254      const std::string& title,
255      const std::string& primary_file_id,
256      google_apis::ResourceEntry::ResourceEntryKind kind) {
257    ScopedVector<google_apis::ResourceEntry> entries;
258    EXPECT_EQ(google_apis::HTTP_SUCCESS,
259              fake_drive_helper_->SearchByTitle(
260                  parent_folder_id, title, &entries));
261    ASSERT_EQ(1u, entries.size());
262    EXPECT_EQ(primary_file_id, entries[0]->resource_id());
263    EXPECT_EQ(kind, entries[0]->kind());
264  }
265
266  void VerifyLocalChangeConsistency(
267      const URLToFileChangesMap& expected_changes) {
268    remote_change_processor_->VerifyConsistency(expected_changes);
269  }
270
271 private:
272  content::TestBrowserThreadBundle thread_bundle_;
273  base::ScopedTempDir database_dir_;
274  scoped_ptr<leveldb::Env> in_memory_env_;
275
276  scoped_ptr<SyncEngineContext> context_;
277  scoped_ptr<FakeDriveServiceHelper> fake_drive_helper_;
278  scoped_ptr<FakeRemoteChangeProcessor> remote_change_processor_;
279
280  scoped_ptr<SyncTaskManager> sync_task_manager_;
281
282  DISALLOW_COPY_AND_ASSIGN(ConflictResolverTest);
283};
284
285TEST_F(ConflictResolverTest, NoFileToBeResolved) {
286  const GURL kOrigin("chrome-extension://example");
287  const std::string sync_root = CreateSyncRoot();
288  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
289  InitializeMetadataDatabase();
290  RegisterApp(kOrigin.host(), app_root);
291  RunRemoteToLocalSyncerUntilIdle();
292
293  EXPECT_EQ(SYNC_STATUS_NO_CONFLICT, RunConflictResolver());
294}
295
296TEST_F(ConflictResolverTest, ResolveConflict_Files) {
297  const GURL kOrigin("chrome-extension://example");
298  const std::string sync_root = CreateSyncRoot();
299  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
300  InitializeMetadataDatabase();
301  RegisterApp(kOrigin.host(), app_root);
302  RunRemoteToLocalSyncerUntilIdle();
303
304  const std::string kTitle = "foo";
305  const std::string primary = CreateRemoteFile(app_root, kTitle, "data1");
306  CreateRemoteFile(app_root, kTitle, "data2");
307  CreateRemoteFile(app_root, kTitle, "data3");
308  CreateRemoteFile(app_root, kTitle, "data4");
309  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
310  RunRemoteToLocalSyncerUntilIdle();
311
312  ScopedVector<google_apis::ResourceEntry> entries =
313      GetResourceEntriesForParentAndTitle(app_root, kTitle);
314  ASSERT_EQ(4u, entries.size());
315
316  // Only primary file should survive.
317  EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
318  VerifyConflictResolution(app_root, kTitle, primary,
319                           google_apis::ResourceEntry::ENTRY_KIND_FILE);
320}
321
322TEST_F(ConflictResolverTest, ResolveConflict_Folders) {
323  const GURL kOrigin("chrome-extension://example");
324  const std::string sync_root = CreateSyncRoot();
325  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
326  InitializeMetadataDatabase();
327  RegisterApp(kOrigin.host(), app_root);
328  RunRemoteToLocalSyncerUntilIdle();
329
330  const std::string kTitle = "foo";
331  const std::string primary = CreateRemoteFolder(app_root, kTitle);
332  CreateRemoteFolder(app_root, kTitle);
333  CreateRemoteFolder(app_root, kTitle);
334  CreateRemoteFolder(app_root, kTitle);
335  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
336  RunRemoteToLocalSyncerUntilIdle();
337
338  ScopedVector<google_apis::ResourceEntry> entries =
339      GetResourceEntriesForParentAndTitle(app_root, kTitle);
340  ASSERT_EQ(4u, entries.size());
341
342  // Only primary file should survive.
343  EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
344  VerifyConflictResolution(app_root, kTitle, primary,
345                           google_apis::ResourceEntry::ENTRY_KIND_FOLDER);
346}
347
348TEST_F(ConflictResolverTest, ResolveConflict_FilesAndFolders) {
349  const GURL kOrigin("chrome-extension://example");
350  const std::string sync_root = CreateSyncRoot();
351  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
352  InitializeMetadataDatabase();
353  RegisterApp(kOrigin.host(), app_root);
354  RunRemoteToLocalSyncerUntilIdle();
355
356  const std::string kTitle = "foo";
357  CreateRemoteFile(app_root, kTitle, "data");
358  const std::string primary = CreateRemoteFolder(app_root, kTitle);
359  CreateRemoteFile(app_root, kTitle, "data2");
360  CreateRemoteFolder(app_root, kTitle);
361  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
362  RunRemoteToLocalSyncerUntilIdle();
363
364  ScopedVector<google_apis::ResourceEntry> entries =
365      GetResourceEntriesForParentAndTitle(app_root, kTitle);
366  ASSERT_EQ(4u, entries.size());
367
368  // Only primary file should survive.
369  EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
370  VerifyConflictResolution(app_root, kTitle, primary,
371                           google_apis::ResourceEntry::ENTRY_KIND_FOLDER);
372}
373
374TEST_F(ConflictResolverTest, ResolveConflict_RemoteFolderOnLocalFile) {
375  const GURL kOrigin("chrome-extension://example");
376  const std::string sync_root = CreateSyncRoot();
377  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
378  InitializeMetadataDatabase();
379  RegisterApp(kOrigin.host(), app_root);
380  RunRemoteToLocalSyncerUntilIdle();
381
382  const std::string kTitle = "foo";
383  storage::FileSystemURL kURL = URL(kOrigin, kTitle);
384
385  // Create a file on local and sync it.
386  CreateLocalFile(kURL);
387  RunLocalToRemoteSyncer(
388      kURL,
389      FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE));
390
391  // Create a folder on remote and sync it.
392  const std::string primary = CreateRemoteFolder(app_root, kTitle);
393  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
394  RunRemoteToLocalSyncerUntilIdle();
395
396  ScopedVector<google_apis::ResourceEntry> entries =
397      GetResourceEntriesForParentAndTitle(app_root, kTitle);
398  ASSERT_EQ(2u, entries.size());
399
400  // Run conflict resolver. Only primary file should survive.
401  EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
402  VerifyConflictResolution(app_root, kTitle, primary,
403                           google_apis::ResourceEntry::ENTRY_KIND_FOLDER);
404
405  // Continue to run remote-to-local sync.
406  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
407  RunRemoteToLocalSyncerUntilIdle();
408
409  // Verify that the local side has been synced to the same state
410  // (i.e. file deletion and folder creation).
411  URLToFileChangesMap expected_changes;
412  expected_changes[kURL].push_back(
413      FileChange(FileChange::FILE_CHANGE_DELETE,
414                 SYNC_FILE_TYPE_UNKNOWN));
415  expected_changes[kURL].push_back(
416      FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
417                 SYNC_FILE_TYPE_DIRECTORY));
418  VerifyLocalChangeConsistency(expected_changes);
419}
420
421TEST_F(ConflictResolverTest, ResolveConflict_RemoteNestedFolderOnLocalFile) {
422  const GURL kOrigin("chrome-extension://example");
423  const std::string sync_root = CreateSyncRoot();
424  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
425  InitializeMetadataDatabase();
426  RegisterApp(kOrigin.host(), app_root);
427  RunRemoteToLocalSyncerUntilIdle();
428
429  const std::string kTitle = "foo";
430  storage::FileSystemURL kURL = URL(kOrigin, kTitle);
431
432  // Create a file on local and sync it.
433  CreateLocalFile(kURL);
434  RunLocalToRemoteSyncer(
435      kURL,
436      FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE));
437
438  // Create a folder and subfolder in it on remote, and sync it.
439  const std::string primary = CreateRemoteFolder(app_root, kTitle);
440  CreateRemoteFolder(primary, "nested");
441  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
442  RunRemoteToLocalSyncerUntilIdle();
443
444  ScopedVector<google_apis::ResourceEntry> entries =
445      GetResourceEntriesForParentAndTitle(app_root, kTitle);
446  ASSERT_EQ(2u, entries.size());
447
448  // Run conflict resolver. Only primary file should survive.
449  EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
450  VerifyConflictResolution(app_root, kTitle, primary,
451                           google_apis::ResourceEntry::ENTRY_KIND_FOLDER);
452
453  // Continue to run remote-to-local sync.
454  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
455  RunRemoteToLocalSyncerUntilIdle();
456
457  // Verify that the local side has been synced to the same state
458  // (i.e. file deletion and folders creation).
459  URLToFileChangesMap expected_changes;
460  expected_changes[kURL].push_back(
461      FileChange(FileChange::FILE_CHANGE_DELETE,
462                 SYNC_FILE_TYPE_UNKNOWN));
463  expected_changes[kURL].push_back(
464      FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
465                 SYNC_FILE_TYPE_DIRECTORY));
466  expected_changes[URL(kOrigin, "foo/nested")].push_back(
467      FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
468                 SYNC_FILE_TYPE_DIRECTORY));
469  VerifyLocalChangeConsistency(expected_changes);
470}
471
472TEST_F(ConflictResolverTest, ResolveMultiParents_File) {
473  const GURL kOrigin("chrome-extension://example");
474  const std::string sync_root = CreateSyncRoot();
475  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
476  InitializeMetadataDatabase();
477  RegisterApp(kOrigin.host(), app_root);
478  RunRemoteToLocalSyncerUntilIdle();
479
480  const std::string primary = CreateRemoteFolder(app_root, "primary");
481  const std::string file = CreateRemoteFile(primary, "file", "data");
482  ASSERT_EQ(google_apis::HTTP_SUCCESS,
483            AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary1"), file));
484  ASSERT_EQ(google_apis::HTTP_SUCCESS,
485            AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary2"), file));
486  ASSERT_EQ(google_apis::HTTP_SUCCESS,
487            AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary3"), file));
488
489  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
490  RunRemoteToLocalSyncerUntilIdle();
491
492  EXPECT_EQ(4, CountParents(file));
493
494  EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
495
496  EXPECT_EQ(1, CountParents(file));
497}
498
499TEST_F(ConflictResolverTest, ResolveMultiParents_Folder) {
500  const GURL kOrigin("chrome-extension://example");
501  const std::string sync_root = CreateSyncRoot();
502  const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host());
503  InitializeMetadataDatabase();
504  RegisterApp(kOrigin.host(), app_root);
505  RunRemoteToLocalSyncerUntilIdle();
506
507  const std::string primary = CreateRemoteFolder(app_root, "primary");
508  const std::string file = CreateRemoteFolder(primary, "folder");
509  ASSERT_EQ(google_apis::HTTP_SUCCESS,
510            AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary1"), file));
511  ASSERT_EQ(google_apis::HTTP_SUCCESS,
512            AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary2"), file));
513  ASSERT_EQ(google_apis::HTTP_SUCCESS,
514            AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary3"), file));
515
516  EXPECT_EQ(SYNC_STATUS_OK, ListChanges());
517  RunRemoteToLocalSyncerUntilIdle();
518
519  EXPECT_EQ(4, CountParents(file));
520
521  EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver());
522
523  EXPECT_EQ(1, CountParents(file));
524}
525
526}  // namespace drive_backend
527}  // namespace sync_file_system
528