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