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