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.
5 #include "chrome/browser/sync_file_system/drive_backend/conflict_resolver.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_error_codes.h"
32 #include "google_apis/drive/drive_api_parser.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"
37 namespace sync_file_system
{
38 namespace drive_backend
{
42 storage::FileSystemURL
URL(const GURL
& origin
, const std::string
& path
) {
43 return CreateSyncableFileSystemURL(
44 origin
, base::FilePath::FromUTF8Unsafe(path
));
49 class ConflictResolverTest
: public testing::Test
{
51 typedef FakeRemoteChangeProcessor::URLToFileChangesMap URLToFileChangesMap
;
53 ConflictResolverTest()
54 : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP
) {}
55 ~ConflictResolverTest() override
{}
57 void SetUp() override
{
58 ASSERT_TRUE(database_dir_
.CreateUniqueTempDir());
59 in_memory_env_
.reset(leveldb::NewMemEnv(leveldb::Env::Default()));
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(),
68 kSyncRootFolderTitle
));
69 remote_change_processor_
.reset(new FakeRemoteChangeProcessor
);
71 context_
.reset(new SyncEngineContext(fake_drive_service
.Pass(),
72 drive_uploader
.Pass(),
73 nullptr /* task_logger */,
74 base::ThreadTaskRunnerHandle::Get(),
75 base::ThreadTaskRunnerHandle::Get(),
76 nullptr /* worker_pool */));
77 context_
->SetRemoteChangeProcessor(remote_change_processor_
.get());
79 RegisterSyncableFileSystem();
81 sync_task_manager_
.reset(new SyncTaskManager(
82 base::WeakPtr
<SyncTaskManager::Client
>(),
83 10 /* maximum_background_task */,
84 base::ThreadTaskRunnerHandle::Get(),
85 nullptr /* worker_pool */));
86 sync_task_manager_
->Initialize(SYNC_STATUS_OK
);
89 void TearDown() override
{
90 sync_task_manager_
.reset();
91 RevokeSyncableFileSystem();
92 fake_drive_helper_
.reset();
94 base::RunLoop().RunUntilIdle();
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(
105 scoped_ptr
<SyncTask
>(initializer
),
106 SyncTaskManager::PRIORITY_MED
,
107 base::Bind(&ConflictResolverTest::DidInitializeMetadataDatabase
,
108 base::Unretained(this), initializer
, &status
));
110 base::RunLoop().RunUntilIdle();
111 EXPECT_EQ(SYNC_STATUS_OK
, status
);
114 void DidInitializeMetadataDatabase(SyncEngineInitializer
* initializer
,
115 SyncStatusCode
* status_out
,
116 SyncStatusCode status
) {
117 context_
->SetMetadataDatabase(initializer
->PassMetadataDatabase());
118 *status_out
= status
;
121 void RegisterApp(const std::string
& app_id
,
122 const std::string
& app_root_folder_id
) {
123 SyncStatusCode status
= context_
->GetMetadataDatabase()->RegisterApp(
124 app_id
, app_root_folder_id
);
125 EXPECT_EQ(SYNC_STATUS_OK
, status
);
129 std::string
CreateSyncRoot() {
130 std::string sync_root_folder_id
;
131 EXPECT_EQ(google_apis::HTTP_CREATED
,
132 fake_drive_helper_
->AddOrphanedFolder(
133 kSyncRootFolderTitle
, &sync_root_folder_id
));
134 return sync_root_folder_id
;
137 std::string
CreateRemoteFolder(const std::string
& parent_folder_id
,
138 const std::string
& title
) {
139 std::string folder_id
;
140 EXPECT_EQ(google_apis::HTTP_CREATED
,
141 fake_drive_helper_
->AddFolder(
142 parent_folder_id
, title
, &folder_id
));
146 std::string
CreateRemoteFile(const std::string
& parent_folder_id
,
147 const std::string
& title
,
148 const std::string
& content
) {
150 EXPECT_EQ(google_apis::HTTP_SUCCESS
,
151 fake_drive_helper_
->AddFile(
152 parent_folder_id
, title
, content
, &file_id
));
156 void CreateLocalFile(const storage::FileSystemURL
& url
) {
157 remote_change_processor_
->UpdateLocalFileMetadata(
158 url
, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE
,
159 SYNC_FILE_TYPE_FILE
));
162 google_apis::DriveApiErrorCode
AddFileToFolder(
163 const std::string
& parent_folder_id
,
164 const std::string
& file_id
) {
165 google_apis::DriveApiErrorCode error
= google_apis::DRIVE_OTHER_ERROR
;
166 context_
->GetDriveService()->AddResourceToDirectory(
167 parent_folder_id
, file_id
,
168 CreateResultReceiver(&error
));
169 base::RunLoop().RunUntilIdle();
173 int CountParents(const std::string
& file_id
) {
174 scoped_ptr
<google_apis::FileResource
> entry
;
175 EXPECT_EQ(google_apis::HTTP_SUCCESS
,
176 fake_drive_helper_
->GetFileResource(file_id
, &entry
));
177 return entry
->parents().size();
180 SyncStatusCode
RunRemoteToLocalSyncer() {
181 SyncStatusCode status
= SYNC_STATUS_UNKNOWN
;
182 scoped_ptr
<RemoteToLocalSyncer
> syncer(
183 new RemoteToLocalSyncer(context_
.get()));
184 syncer
->RunPreflight(SyncTaskToken::CreateForTesting(
185 CreateResultReceiver(&status
)));
186 base::RunLoop().RunUntilIdle();
190 SyncStatusCode
RunLocalToRemoteSyncer(const storage::FileSystemURL
& url
,
191 const FileChange
& file_change
) {
192 SyncStatusCode status
= SYNC_STATUS_UNKNOWN
;
193 base::FilePath local_path
= base::FilePath(FILE_PATH_LITERAL("dummy"));
194 if (file_change
.IsAddOrUpdate())
195 CreateTemporaryFileInDir(database_dir_
.path(), &local_path
);
196 scoped_ptr
<LocalToRemoteSyncer
> syncer(new LocalToRemoteSyncer(
198 SyncFileMetadata(file_change
.file_type(), 0, base::Time()),
199 file_change
, local_path
, url
));
200 syncer
->RunPreflight(SyncTaskToken::CreateForTesting(
201 CreateResultReceiver(&status
)));
202 base::RunLoop().RunUntilIdle();
203 if (status
== SYNC_STATUS_OK
)
204 remote_change_processor_
->ClearLocalChanges(url
);
208 void RunRemoteToLocalSyncerUntilIdle() {
209 const int kRetryLimit
= 100;
210 SyncStatusCode status
;
212 MetadataDatabase
* metadata_database
= context_
->GetMetadataDatabase();
214 if (retry_count
++ > kRetryLimit
)
216 status
= RunRemoteToLocalSyncer();
217 } while (status
== SYNC_STATUS_OK
||
218 status
== SYNC_STATUS_RETRY
||
219 metadata_database
->PromoteDemotedTrackers());
220 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC
, status
);
223 SyncStatusCode
RunConflictResolver() {
224 SyncStatusCode status
= SYNC_STATUS_UNKNOWN
;
225 ConflictResolver
resolver(context_
.get());
226 resolver
.RunPreflight(SyncTaskToken::CreateForTesting(
227 CreateResultReceiver(&status
)));
228 base::RunLoop().RunUntilIdle();
232 SyncStatusCode
ListChanges() {
233 SyncStatusCode status
= SYNC_STATUS_UNKNOWN
;
234 sync_task_manager_
->ScheduleSyncTask(
236 scoped_ptr
<SyncTask
>(new ListChangesTask(context_
.get())),
237 SyncTaskManager::PRIORITY_MED
,
238 CreateResultReceiver(&status
));
239 base::RunLoop().RunUntilIdle();
243 ScopedVector
<google_apis::FileResource
>
244 GetResourceEntriesForParentAndTitle(const std::string
& parent_folder_id
,
245 const std::string
& title
) {
246 ScopedVector
<google_apis::FileResource
> entries
;
247 EXPECT_EQ(google_apis::HTTP_SUCCESS
,
248 fake_drive_helper_
->SearchByTitle(
249 parent_folder_id
, title
, &entries
));
250 return entries
.Pass();
253 void VerifyConflictResolution(
254 const std::string
& parent_folder_id
,
255 const std::string
& title
,
256 const std::string
& primary_file_id
,
257 test_util::FileResourceKind kind
) {
258 ScopedVector
<google_apis::FileResource
> entries
;
259 EXPECT_EQ(google_apis::HTTP_SUCCESS
,
260 fake_drive_helper_
->SearchByTitle(
261 parent_folder_id
, title
, &entries
));
262 ASSERT_EQ(1u, entries
.size());
263 EXPECT_EQ(primary_file_id
, entries
[0]->file_id());
264 EXPECT_EQ(kind
, test_util::GetFileResourceKind(*entries
[0]));
267 void VerifyLocalChangeConsistency(
268 const URLToFileChangesMap
& expected_changes
) {
269 remote_change_processor_
->VerifyConsistency(expected_changes
);
273 content::TestBrowserThreadBundle thread_bundle_
;
274 base::ScopedTempDir database_dir_
;
275 scoped_ptr
<leveldb::Env
> in_memory_env_
;
277 scoped_ptr
<SyncEngineContext
> context_
;
278 scoped_ptr
<FakeDriveServiceHelper
> fake_drive_helper_
;
279 scoped_ptr
<FakeRemoteChangeProcessor
> remote_change_processor_
;
281 scoped_ptr
<SyncTaskManager
> sync_task_manager_
;
283 DISALLOW_COPY_AND_ASSIGN(ConflictResolverTest
);
286 TEST_F(ConflictResolverTest
, NoFileToBeResolved
) {
287 const GURL
kOrigin("chrome-extension://example");
288 const std::string sync_root
= CreateSyncRoot();
289 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
290 InitializeMetadataDatabase();
291 RegisterApp(kOrigin
.host(), app_root
);
292 RunRemoteToLocalSyncerUntilIdle();
294 EXPECT_EQ(SYNC_STATUS_NO_CONFLICT
, RunConflictResolver());
297 TEST_F(ConflictResolverTest
, ResolveConflict_Files
) {
298 const GURL
kOrigin("chrome-extension://example");
299 const std::string sync_root
= CreateSyncRoot();
300 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
301 InitializeMetadataDatabase();
302 RegisterApp(kOrigin
.host(), app_root
);
303 RunRemoteToLocalSyncerUntilIdle();
305 const std::string kTitle
= "foo";
306 const std::string primary
= CreateRemoteFile(app_root
, kTitle
, "data1");
307 CreateRemoteFile(app_root
, kTitle
, "data2");
308 CreateRemoteFile(app_root
, kTitle
, "data3");
309 CreateRemoteFile(app_root
, kTitle
, "data4");
310 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
311 RunRemoteToLocalSyncerUntilIdle();
313 ScopedVector
<google_apis::FileResource
> entries
=
314 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
315 ASSERT_EQ(4u, entries
.size());
317 // Only primary file should survive.
318 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
319 VerifyConflictResolution(app_root
, kTitle
, primary
,
320 test_util::RESOURCE_KIND_FILE
);
323 TEST_F(ConflictResolverTest
, ResolveConflict_Folders
) {
324 const GURL
kOrigin("chrome-extension://example");
325 const std::string sync_root
= CreateSyncRoot();
326 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
327 InitializeMetadataDatabase();
328 RegisterApp(kOrigin
.host(), app_root
);
329 RunRemoteToLocalSyncerUntilIdle();
331 const std::string kTitle
= "foo";
332 const std::string primary
= CreateRemoteFolder(app_root
, kTitle
);
333 CreateRemoteFolder(app_root
, kTitle
);
334 CreateRemoteFolder(app_root
, kTitle
);
335 CreateRemoteFolder(app_root
, kTitle
);
336 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
337 RunRemoteToLocalSyncerUntilIdle();
339 ScopedVector
<google_apis::FileResource
> entries
=
340 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
341 ASSERT_EQ(4u, entries
.size());
343 // Only primary file should survive.
344 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
345 VerifyConflictResolution(app_root
, kTitle
, primary
,
346 test_util::RESOURCE_KIND_FOLDER
);
349 TEST_F(ConflictResolverTest
, ResolveConflict_FilesAndFolders
) {
350 const GURL
kOrigin("chrome-extension://example");
351 const std::string sync_root
= CreateSyncRoot();
352 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
353 InitializeMetadataDatabase();
354 RegisterApp(kOrigin
.host(), app_root
);
355 RunRemoteToLocalSyncerUntilIdle();
357 const std::string kTitle
= "foo";
358 CreateRemoteFile(app_root
, kTitle
, "data");
359 const std::string primary
= CreateRemoteFolder(app_root
, kTitle
);
360 CreateRemoteFile(app_root
, kTitle
, "data2");
361 CreateRemoteFolder(app_root
, kTitle
);
362 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
363 RunRemoteToLocalSyncerUntilIdle();
365 ScopedVector
<google_apis::FileResource
> entries
=
366 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
367 ASSERT_EQ(4u, entries
.size());
369 // Only primary file should survive.
370 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
371 VerifyConflictResolution(app_root
, kTitle
, primary
,
372 test_util::RESOURCE_KIND_FOLDER
);
375 TEST_F(ConflictResolverTest
, ResolveConflict_RemoteFolderOnLocalFile
) {
376 const GURL
kOrigin("chrome-extension://example");
377 const std::string sync_root
= CreateSyncRoot();
378 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
379 InitializeMetadataDatabase();
380 RegisterApp(kOrigin
.host(), app_root
);
381 RunRemoteToLocalSyncerUntilIdle();
383 const std::string kTitle
= "foo";
384 storage::FileSystemURL kURL
= URL(kOrigin
, kTitle
);
386 // Create a file on local and sync it.
387 CreateLocalFile(kURL
);
388 RunLocalToRemoteSyncer(
390 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE
, SYNC_FILE_TYPE_FILE
));
392 // Create a folder on remote and sync it.
393 const std::string primary
= CreateRemoteFolder(app_root
, kTitle
);
394 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
395 RunRemoteToLocalSyncerUntilIdle();
397 ScopedVector
<google_apis::FileResource
> entries
=
398 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
399 ASSERT_EQ(2u, entries
.size());
401 // Run conflict resolver. Only primary file should survive.
402 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
403 VerifyConflictResolution(app_root
, kTitle
, primary
,
404 test_util::RESOURCE_KIND_FOLDER
);
406 // Continue to run remote-to-local sync.
407 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
408 RunRemoteToLocalSyncerUntilIdle();
410 // Verify that the local side has been synced to the same state
411 // (i.e. file deletion and folder creation).
412 URLToFileChangesMap expected_changes
;
413 expected_changes
[kURL
].push_back(
414 FileChange(FileChange::FILE_CHANGE_DELETE
,
415 SYNC_FILE_TYPE_UNKNOWN
));
416 expected_changes
[kURL
].push_back(
417 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE
,
418 SYNC_FILE_TYPE_DIRECTORY
));
419 VerifyLocalChangeConsistency(expected_changes
);
422 TEST_F(ConflictResolverTest
, ResolveConflict_RemoteNestedFolderOnLocalFile
) {
423 const GURL
kOrigin("chrome-extension://example");
424 const std::string sync_root
= CreateSyncRoot();
425 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
426 InitializeMetadataDatabase();
427 RegisterApp(kOrigin
.host(), app_root
);
428 RunRemoteToLocalSyncerUntilIdle();
430 const std::string kTitle
= "foo";
431 storage::FileSystemURL kURL
= URL(kOrigin
, kTitle
);
433 // Create a file on local and sync it.
434 CreateLocalFile(kURL
);
435 RunLocalToRemoteSyncer(
437 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE
, SYNC_FILE_TYPE_FILE
));
439 // Create a folder and subfolder in it on remote, and sync it.
440 const std::string primary
= CreateRemoteFolder(app_root
, kTitle
);
441 CreateRemoteFolder(primary
, "nested");
442 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
443 RunRemoteToLocalSyncerUntilIdle();
445 ScopedVector
<google_apis::FileResource
> entries
=
446 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
447 ASSERT_EQ(2u, entries
.size());
449 // Run conflict resolver. Only primary file should survive.
450 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
451 VerifyConflictResolution(app_root
, kTitle
, primary
,
452 test_util::RESOURCE_KIND_FOLDER
);
454 // Continue to run remote-to-local sync.
455 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
456 RunRemoteToLocalSyncerUntilIdle();
458 // Verify that the local side has been synced to the same state
459 // (i.e. file deletion and folders creation).
460 URLToFileChangesMap expected_changes
;
461 expected_changes
[kURL
].push_back(
462 FileChange(FileChange::FILE_CHANGE_DELETE
,
463 SYNC_FILE_TYPE_UNKNOWN
));
464 expected_changes
[kURL
].push_back(
465 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE
,
466 SYNC_FILE_TYPE_DIRECTORY
));
467 expected_changes
[URL(kOrigin
, "foo/nested")].push_back(
468 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE
,
469 SYNC_FILE_TYPE_DIRECTORY
));
470 VerifyLocalChangeConsistency(expected_changes
);
473 TEST_F(ConflictResolverTest
, ResolveMultiParents_File
) {
474 const GURL
kOrigin("chrome-extension://example");
475 const std::string sync_root
= CreateSyncRoot();
476 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
477 InitializeMetadataDatabase();
478 RegisterApp(kOrigin
.host(), app_root
);
479 RunRemoteToLocalSyncerUntilIdle();
481 const std::string primary
= CreateRemoteFolder(app_root
, "primary");
482 const std::string file
= CreateRemoteFile(primary
, "file", "data");
483 ASSERT_EQ(google_apis::HTTP_SUCCESS
,
484 AddFileToFolder(CreateRemoteFolder(app_root
, "nonprimary1"), file
));
485 ASSERT_EQ(google_apis::HTTP_SUCCESS
,
486 AddFileToFolder(CreateRemoteFolder(app_root
, "nonprimary2"), file
));
487 ASSERT_EQ(google_apis::HTTP_SUCCESS
,
488 AddFileToFolder(CreateRemoteFolder(app_root
, "nonprimary3"), file
));
490 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
491 RunRemoteToLocalSyncerUntilIdle();
493 EXPECT_EQ(4, CountParents(file
));
495 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
497 EXPECT_EQ(1, CountParents(file
));
500 TEST_F(ConflictResolverTest
, ResolveMultiParents_Folder
) {
501 const GURL
kOrigin("chrome-extension://example");
502 const std::string sync_root
= CreateSyncRoot();
503 const std::string app_root
= CreateRemoteFolder(sync_root
, kOrigin
.host());
504 InitializeMetadataDatabase();
505 RegisterApp(kOrigin
.host(), app_root
);
506 RunRemoteToLocalSyncerUntilIdle();
508 const std::string primary
= CreateRemoteFolder(app_root
, "primary");
509 const std::string file
= CreateRemoteFolder(primary
, "folder");
510 ASSERT_EQ(google_apis::HTTP_SUCCESS
,
511 AddFileToFolder(CreateRemoteFolder(app_root
, "nonprimary1"), file
));
512 ASSERT_EQ(google_apis::HTTP_SUCCESS
,
513 AddFileToFolder(CreateRemoteFolder(app_root
, "nonprimary2"), file
));
514 ASSERT_EQ(google_apis::HTTP_SUCCESS
,
515 AddFileToFolder(CreateRemoteFolder(app_root
, "nonprimary3"), file
));
517 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
518 RunRemoteToLocalSyncerUntilIdle();
520 EXPECT_EQ(4, CountParents(file
));
522 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
524 EXPECT_EQ(1, CountParents(file
));
527 } // namespace drive_backend
528 } // namespace sync_file_system