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