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