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