conflict_resolver_unittest.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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 fake_drive_service_.reset(new FakeDriveServiceWrapper); 60 61 drive_uploader_.reset(new FakeDriveUploader(fake_drive_service_.get())); 62 fake_drive_helper_.reset( 63 new FakeDriveServiceHelper(fake_drive_service_.get(), 64 drive_uploader_.get(), 65 kSyncRootFolderTitle)); 66 fake_remote_change_processor_.reset(new FakeRemoteChangeProcessor); 67 68 context_.reset(new SyncEngineContext( 69 fake_drive_service_.get(), 70 drive_uploader_.get(), 71 base::MessageLoopProxy::current(), 72 base::MessageLoopProxy::current(), 73 base::MessageLoopProxy::current())); 74 context_->SetRemoteChangeProcessor(fake_remote_change_processor_.get()); 75 76 RegisterSyncableFileSystem(); 77 78 sync_task_manager_.reset(new SyncTaskManager( 79 base::WeakPtr<SyncTaskManager::Client>(), 80 10 /* maximum_background_task */)); 81 sync_task_manager_->Initialize(SYNC_STATUS_OK); 82 } 83 84 virtual void TearDown() OVERRIDE { 85 sync_task_manager_.reset(); 86 fake_drive_service_.reset(); 87 drive_uploader_.reset(); 88 89 RevokeSyncableFileSystem(); 90 91 fake_remote_change_processor_.reset(); 92 fake_drive_helper_.reset(); 93 context_.reset(); 94 base::RunLoop().RunUntilIdle(); 95 } 96 97 void InitializeMetadataDatabase() { 98 SyncEngineInitializer* initializer = 99 new SyncEngineInitializer(context_.get(), 100 base::MessageLoopProxy::current(), 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 fake_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::ResourceEntry> entry; 178 EXPECT_EQ(google_apis::HTTP_SUCCESS, 179 fake_drive_helper_->GetResourceEntry(file_id, &entry)); 180 int count = 0; 181 const ScopedVector<google_apis::Link>& links = entry->links(); 182 for (ScopedVector<google_apis::Link>::const_iterator itr = links.begin(); 183 itr != links.end(); ++itr) { 184 if ((*itr)->type() == google_apis::Link::LINK_PARENT) 185 ++count; 186 } 187 return count; 188 } 189 190 SyncStatusCode RunRemoteToLocalSyncer() { 191 SyncStatusCode status = SYNC_STATUS_UNKNOWN; 192 scoped_ptr<RemoteToLocalSyncer> syncer( 193 new RemoteToLocalSyncer(context_.get())); 194 syncer->RunExclusive(CreateResultReceiver(&status)); 195 base::RunLoop().RunUntilIdle(); 196 return status; 197 } 198 199 SyncStatusCode RunLocalToRemoteSyncer( 200 const fileapi::FileSystemURL& url, 201 const FileChange& file_change) { 202 SyncStatusCode status = SYNC_STATUS_UNKNOWN; 203 base::FilePath local_path = base::FilePath(FILE_PATH_LITERAL("dummy")); 204 if (file_change.IsAddOrUpdate()) 205 CreateTemporaryFileInDir(database_dir_.path(), &local_path); 206 scoped_ptr<LocalToRemoteSyncer> syncer(new LocalToRemoteSyncer( 207 context_.get(), 208 SyncFileMetadata(file_change.file_type(), 0, base::Time()), 209 file_change, local_path, url)); 210 syncer->RunExclusive(CreateResultReceiver(&status)); 211 base::RunLoop().RunUntilIdle(); 212 if (status == SYNC_STATUS_OK) 213 fake_remote_change_processor_->ClearLocalChanges(url); 214 return status; 215 } 216 217 void RunRemoteToLocalSyncerUntilIdle() { 218 SyncStatusCode status = SYNC_STATUS_UNKNOWN; 219 while (status != SYNC_STATUS_NO_CHANGE_TO_SYNC) 220 status = RunRemoteToLocalSyncer(); 221 } 222 223 SyncStatusCode RunConflictResolver() { 224 SyncStatusCode status = SYNC_STATUS_UNKNOWN; 225 ConflictResolver resolver(context_.get()); 226 resolver.RunExclusive(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(const std::string& parent_folder_id, 253 const std::string& title, 254 const std::string& primary_file_id, 255 google_apis::DriveEntryKind kind) { 256 ScopedVector<google_apis::ResourceEntry> entries; 257 EXPECT_EQ(google_apis::HTTP_SUCCESS, 258 fake_drive_helper_->SearchByTitle( 259 parent_folder_id, title, &entries)); 260 ASSERT_EQ(1u, entries.size()); 261 EXPECT_EQ(primary_file_id, entries[0]->resource_id()); 262 EXPECT_EQ(kind, entries[0]->kind()); 263 } 264 265 void VerifyLocalChangeConsistency( 266 const URLToFileChangesMap& expected_changes) { 267 fake_remote_change_processor_->VerifyConsistency(expected_changes); 268 } 269 270 private: 271 content::TestBrowserThreadBundle thread_bundle_; 272 base::ScopedTempDir database_dir_; 273 scoped_ptr<leveldb::Env> in_memory_env_; 274 275 scoped_ptr<SyncEngineContext> context_; 276 scoped_ptr<FakeDriveServiceWrapper> fake_drive_service_; 277 scoped_ptr<drive::DriveUploaderInterface> drive_uploader_; 278 scoped_ptr<FakeDriveServiceHelper> fake_drive_helper_; 279 scoped_ptr<FakeRemoteChangeProcessor> fake_remote_change_processor_; 280 281 scoped_ptr<SyncTaskManager> sync_task_manager_; 282 283 DISALLOW_COPY_AND_ASSIGN(ConflictResolverTest); 284}; 285 286TEST_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(); 293 294 EXPECT_EQ(SYNC_STATUS_NO_CONFLICT, RunConflictResolver()); 295} 296 297TEST_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(); 304 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(); 312 313 ScopedVector<google_apis::ResourceEntry> entries = 314 GetResourceEntriesForParentAndTitle(app_root, kTitle); 315 ASSERT_EQ(4u, entries.size()); 316 317 // Only primary file should survive. 318 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver()); 319 VerifyConflictResolution(app_root, kTitle, primary, 320 google_apis::ENTRY_KIND_FILE); 321} 322 323TEST_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(); 330 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(); 338 339 ScopedVector<google_apis::ResourceEntry> entries = 340 GetResourceEntriesForParentAndTitle(app_root, kTitle); 341 ASSERT_EQ(4u, entries.size()); 342 343 // Only primary file should survive. 344 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver()); 345 VerifyConflictResolution(app_root, kTitle, primary, 346 google_apis::ENTRY_KIND_FOLDER); 347} 348 349TEST_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(); 356 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(); 364 365 ScopedVector<google_apis::ResourceEntry> entries = 366 GetResourceEntriesForParentAndTitle(app_root, kTitle); 367 ASSERT_EQ(4u, entries.size()); 368 369 // Only primary file should survive. 370 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver()); 371 VerifyConflictResolution(app_root, kTitle, primary, 372 google_apis::ENTRY_KIND_FOLDER); 373} 374 375TEST_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(); 382 383 const std::string kTitle = "foo"; 384 fileapi::FileSystemURL kURL = URL(kOrigin, kTitle); 385 386 // Create a file on local and sync it. 387 CreateLocalFile(kURL); 388 RunLocalToRemoteSyncer( 389 kURL, 390 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE)); 391 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(); 396 397 ScopedVector<google_apis::ResourceEntry> entries = 398 GetResourceEntriesForParentAndTitle(app_root, kTitle); 399 ASSERT_EQ(2u, entries.size()); 400 401 // Run conflict resolver. Only primary file should survive. 402 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver()); 403 VerifyConflictResolution(app_root, kTitle, primary, 404 google_apis::ENTRY_KIND_FOLDER); 405 406 // Continue to run remote-to-local sync. 407 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 408 RunRemoteToLocalSyncerUntilIdle(); 409 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); 420} 421 422TEST_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(); 429 430 const std::string kTitle = "foo"; 431 fileapi::FileSystemURL kURL = URL(kOrigin, kTitle); 432 433 // Create a file on local and sync it. 434 CreateLocalFile(kURL); 435 RunLocalToRemoteSyncer( 436 kURL, 437 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE)); 438 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(); 444 445 ScopedVector<google_apis::ResourceEntry> entries = 446 GetResourceEntriesForParentAndTitle(app_root, kTitle); 447 ASSERT_EQ(2u, entries.size()); 448 449 // Run conflict resolver. Only primary file should survive. 450 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver()); 451 VerifyConflictResolution(app_root, kTitle, primary, 452 google_apis::ENTRY_KIND_FOLDER); 453 454 // Continue to run remote-to-local sync. 455 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 456 RunRemoteToLocalSyncerUntilIdle(); 457 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); 471} 472 473TEST_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(); 480 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)); 489 490 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 491 RunRemoteToLocalSyncerUntilIdle(); 492 493 EXPECT_EQ(4, CountParents(file)); 494 495 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver()); 496 497 EXPECT_EQ(1, CountParents(file)); 498} 499 500TEST_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(); 507 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)); 516 517 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 518 RunRemoteToLocalSyncerUntilIdle(); 519 520 EXPECT_EQ(4, CountParents(file)); 521 522 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver()); 523 524 EXPECT_EQ(1, CountParents(file)); 525} 526 527} // namespace drive_backend 528} // namespace sync_file_system 529