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_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"
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 virtual ~ConflictResolverTest() {}
57 virtual 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(
72 fake_drive_service
.PassAs
<drive::DriveServiceInterface
>(),
73 drive_uploader
.Pass(),
75 base::ThreadTaskRunnerHandle::Get(),
76 base::ThreadTaskRunnerHandle::Get()));
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 sync_task_manager_
->Initialize(SYNC_STATUS_OK
);
88 virtual void TearDown() OVERRIDE
{
89 sync_task_manager_
.reset();
90 RevokeSyncableFileSystem();
91 fake_drive_helper_
.reset();
93 base::RunLoop().RunUntilIdle();
96 void InitializeMetadataDatabase() {
97 SyncEngineInitializer
* initializer
=
98 new SyncEngineInitializer(context_
.get(),
100 in_memory_env_
.get());
101 SyncStatusCode status
= SYNC_STATUS_UNKNOWN
;
102 sync_task_manager_
->ScheduleSyncTask(
104 scoped_ptr
<SyncTask
>(initializer
),
105 SyncTaskManager::PRIORITY_MED
,
106 base::Bind(&ConflictResolverTest::DidInitializeMetadataDatabase
,
107 base::Unretained(this), initializer
, &status
));
109 base::RunLoop().RunUntilIdle();
110 EXPECT_EQ(SYNC_STATUS_OK
, status
);
113 void DidInitializeMetadataDatabase(SyncEngineInitializer
* initializer
,
114 SyncStatusCode
* status_out
,
115 SyncStatusCode status
) {
116 context_
->SetMetadataDatabase(initializer
->PassMetadataDatabase());
117 *status_out
= status
;
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
);
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
;
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
));
147 std::string
CreateRemoteFile(const std::string
& parent_folder_id
,
148 const std::string
& title
,
149 const std::string
& content
) {
151 EXPECT_EQ(google_apis::HTTP_SUCCESS
,
152 fake_drive_helper_
->AddFile(
153 parent_folder_id
, title
, content
, &file_id
));
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
));
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();
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();
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();
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(
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
);
209 void RunRemoteToLocalSyncerUntilIdle() {
210 const int kRetryLimit
= 100;
211 SyncStatusCode status
;
213 MetadataDatabase
* metadata_database
= context_
->GetMetadataDatabase();
215 if (retry_count
++ > kRetryLimit
)
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
);
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();
233 SyncStatusCode
ListChanges() {
234 SyncStatusCode status
= SYNC_STATUS_UNKNOWN
;
235 sync_task_manager_
->ScheduleSyncTask(
237 scoped_ptr
<SyncTask
>(new ListChangesTask(context_
.get())),
238 SyncTaskManager::PRIORITY_MED
,
239 CreateResultReceiver(&status
));
240 base::RunLoop().RunUntilIdle();
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();
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());
268 void VerifyLocalChangeConsistency(
269 const URLToFileChangesMap
& expected_changes
) {
270 remote_change_processor_
->VerifyConsistency(expected_changes
);
274 content::TestBrowserThreadBundle thread_bundle_
;
275 base::ScopedTempDir database_dir_
;
276 scoped_ptr
<leveldb::Env
> in_memory_env_
;
278 scoped_ptr
<SyncEngineContext
> context_
;
279 scoped_ptr
<FakeDriveServiceHelper
> fake_drive_helper_
;
280 scoped_ptr
<FakeRemoteChangeProcessor
> remote_change_processor_
;
282 scoped_ptr
<SyncTaskManager
> sync_task_manager_
;
284 DISALLOW_COPY_AND_ASSIGN(ConflictResolverTest
);
287 TEST_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();
295 EXPECT_EQ(SYNC_STATUS_NO_CONFLICT
, RunConflictResolver());
298 TEST_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();
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();
314 ScopedVector
<google_apis::ResourceEntry
> entries
=
315 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
316 ASSERT_EQ(4u, entries
.size());
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
);
324 TEST_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();
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();
340 ScopedVector
<google_apis::ResourceEntry
> entries
=
341 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
342 ASSERT_EQ(4u, entries
.size());
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
);
350 TEST_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();
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();
366 ScopedVector
<google_apis::ResourceEntry
> entries
=
367 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
368 ASSERT_EQ(4u, entries
.size());
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
);
376 TEST_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();
384 const std::string kTitle
= "foo";
385 storage::FileSystemURL kURL
= URL(kOrigin
, kTitle
);
387 // Create a file on local and sync it.
388 CreateLocalFile(kURL
);
389 RunLocalToRemoteSyncer(
391 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE
, SYNC_FILE_TYPE_FILE
));
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();
398 ScopedVector
<google_apis::ResourceEntry
> entries
=
399 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
400 ASSERT_EQ(2u, entries
.size());
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
);
407 // Continue to run remote-to-local sync.
408 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
409 RunRemoteToLocalSyncerUntilIdle();
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
);
423 TEST_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();
431 const std::string kTitle
= "foo";
432 storage::FileSystemURL kURL
= URL(kOrigin
, kTitle
);
434 // Create a file on local and sync it.
435 CreateLocalFile(kURL
);
436 RunLocalToRemoteSyncer(
438 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE
, SYNC_FILE_TYPE_FILE
));
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();
446 ScopedVector
<google_apis::ResourceEntry
> entries
=
447 GetResourceEntriesForParentAndTitle(app_root
, kTitle
);
448 ASSERT_EQ(2u, entries
.size());
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
);
455 // Continue to run remote-to-local sync.
456 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
457 RunRemoteToLocalSyncerUntilIdle();
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
);
474 TEST_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();
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
));
491 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
492 RunRemoteToLocalSyncerUntilIdle();
494 EXPECT_EQ(4, CountParents(file
));
496 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
498 EXPECT_EQ(1, CountParents(file
));
501 TEST_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();
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
));
518 EXPECT_EQ(SYNC_STATUS_OK
, ListChanges());
519 RunRemoteToLocalSyncerUntilIdle();
521 EXPECT_EQ(4, CountParents(file
));
523 EXPECT_EQ(SYNC_STATUS_OK
, RunConflictResolver());
525 EXPECT_EQ(1, CountParents(file
));
528 } // namespace drive_backend
529 } // namespace sync_file_system