conflict_resolver_unittest.cc revision a02191e04bc25c4935f804f2c080ae28663d096d
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 context_->SetRemoteChangeProcessor(fake_remote_change_processor_.get()); 73 74 RegisterSyncableFileSystem(); 75 76 sync_task_manager_.reset(new SyncTaskManager( 77 base::WeakPtr<SyncTaskManager::Client>(), 78 10 /* maximum_background_task */)); 79 sync_task_manager_->Initialize(SYNC_STATUS_OK); 80 } 81 82 virtual void TearDown() OVERRIDE { 83 sync_task_manager_.reset(); 84 fake_drive_service_.reset(); 85 drive_uploader_.reset(); 86 87 RevokeSyncableFileSystem(); 88 89 fake_remote_change_processor_.reset(); 90 fake_drive_helper_.reset(); 91 context_.reset(); 92 base::RunLoop().RunUntilIdle(); 93 } 94 95 void InitializeMetadataDatabase() { 96 SyncEngineInitializer* initializer = 97 new SyncEngineInitializer(context_.get(), 98 base::MessageLoopProxy::current(), 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 = 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); 127 } 128 129 protected: 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; 136 } 137 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)); 144 return folder_id; 145 } 146 147 std::string CreateRemoteFile(const std::string& parent_folder_id, 148 const std::string& title, 149 const std::string& content) { 150 std::string file_id; 151 EXPECT_EQ(google_apis::HTTP_SUCCESS, 152 fake_drive_helper_->AddFile( 153 parent_folder_id, title, content, &file_id)); 154 return file_id; 155 } 156 157 void CreateLocalFile(const fileapi::FileSystemURL& url) { 158 fake_remote_change_processor_->UpdateLocalFileMetadata( 159 url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, 160 SYNC_FILE_TYPE_FILE)); 161 } 162 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(); 171 return error; 172 } 173 174 int CountParents(const std::string& file_id) { 175 scoped_ptr<google_apis::ResourceEntry> entry; 176 EXPECT_EQ(google_apis::HTTP_SUCCESS, 177 fake_drive_helper_->GetResourceEntry(file_id, &entry)); 178 int count = 0; 179 const ScopedVector<google_apis::Link>& links = entry->links(); 180 for (ScopedVector<google_apis::Link>::const_iterator itr = links.begin(); 181 itr != links.end(); ++itr) { 182 if ((*itr)->type() == google_apis::Link::LINK_PARENT) 183 ++count; 184 } 185 return count; 186 } 187 188 SyncStatusCode RunRemoteToLocalSyncer() { 189 SyncStatusCode status = SYNC_STATUS_UNKNOWN; 190 scoped_ptr<RemoteToLocalSyncer> syncer( 191 new RemoteToLocalSyncer(context_.get())); 192 syncer->RunExclusive(CreateResultReceiver(&status)); 193 base::RunLoop().RunUntilIdle(); 194 return status; 195 } 196 197 SyncStatusCode RunLocalToRemoteSyncer( 198 const fileapi::FileSystemURL& url, 199 const FileChange& file_change) { 200 SyncStatusCode status = SYNC_STATUS_UNKNOWN; 201 base::FilePath local_path = base::FilePath(FILE_PATH_LITERAL("dummy")); 202 if (file_change.IsAddOrUpdate()) 203 CreateTemporaryFileInDir(database_dir_.path(), &local_path); 204 scoped_ptr<LocalToRemoteSyncer> syncer(new LocalToRemoteSyncer( 205 context_.get(), 206 SyncFileMetadata(file_change.file_type(), 0, base::Time()), 207 file_change, local_path, url)); 208 syncer->RunExclusive(CreateResultReceiver(&status)); 209 base::RunLoop().RunUntilIdle(); 210 if (status == SYNC_STATUS_OK) 211 fake_remote_change_processor_->ClearLocalChanges(url); 212 return status; 213 } 214 215 void RunRemoteToLocalSyncerUntilIdle() { 216 SyncStatusCode status = SYNC_STATUS_UNKNOWN; 217 while (status != SYNC_STATUS_NO_CHANGE_TO_SYNC) 218 status = RunRemoteToLocalSyncer(); 219 } 220 221 SyncStatusCode RunConflictResolver() { 222 SyncStatusCode status = SYNC_STATUS_UNKNOWN; 223 ConflictResolver resolver(context_.get()); 224 resolver.RunExclusive(CreateResultReceiver(&status)); 225 base::RunLoop().RunUntilIdle(); 226 return status; 227 } 228 229 SyncStatusCode ListChanges() { 230 SyncStatusCode status = SYNC_STATUS_UNKNOWN; 231 sync_task_manager_->ScheduleSyncTask( 232 FROM_HERE, 233 scoped_ptr<SyncTask>(new ListChangesTask(context_.get())), 234 SyncTaskManager::PRIORITY_MED, 235 CreateResultReceiver(&status)); 236 base::RunLoop().RunUntilIdle(); 237 return status; 238 } 239 240 ScopedVector<google_apis::ResourceEntry> 241 GetResourceEntriesForParentAndTitle(const std::string& parent_folder_id, 242 const std::string& title) { 243 ScopedVector<google_apis::ResourceEntry> entries; 244 EXPECT_EQ(google_apis::HTTP_SUCCESS, 245 fake_drive_helper_->SearchByTitle( 246 parent_folder_id, title, &entries)); 247 return entries.Pass(); 248 } 249 250 void VerifyConflictResolution(const std::string& parent_folder_id, 251 const std::string& title, 252 const std::string& primary_file_id, 253 google_apis::DriveEntryKind kind) { 254 ScopedVector<google_apis::ResourceEntry> entries; 255 EXPECT_EQ(google_apis::HTTP_SUCCESS, 256 fake_drive_helper_->SearchByTitle( 257 parent_folder_id, title, &entries)); 258 ASSERT_EQ(1u, entries.size()); 259 EXPECT_EQ(primary_file_id, entries[0]->resource_id()); 260 EXPECT_EQ(kind, entries[0]->kind()); 261 } 262 263 void VerifyLocalChangeConsistency( 264 const URLToFileChangesMap& expected_changes) { 265 fake_remote_change_processor_->VerifyConsistency(expected_changes); 266 } 267 268 private: 269 content::TestBrowserThreadBundle thread_bundle_; 270 base::ScopedTempDir database_dir_; 271 scoped_ptr<leveldb::Env> in_memory_env_; 272 273 scoped_ptr<SyncEngineContext> context_; 274 scoped_ptr<FakeDriveServiceWrapper> fake_drive_service_; 275 scoped_ptr<drive::DriveUploaderInterface> drive_uploader_; 276 scoped_ptr<FakeDriveServiceHelper> fake_drive_helper_; 277 scoped_ptr<FakeRemoteChangeProcessor> fake_remote_change_processor_; 278 279 scoped_ptr<SyncTaskManager> sync_task_manager_; 280 281 DISALLOW_COPY_AND_ASSIGN(ConflictResolverTest); 282}; 283 284TEST_F(ConflictResolverTest, NoFileToBeResolved) { 285 const GURL kOrigin("chrome-extension://example"); 286 const std::string sync_root = CreateSyncRoot(); 287 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 288 InitializeMetadataDatabase(); 289 RegisterApp(kOrigin.host(), app_root); 290 RunRemoteToLocalSyncerUntilIdle(); 291 292 EXPECT_EQ(SYNC_STATUS_NO_CONFLICT, RunConflictResolver()); 293} 294 295TEST_F(ConflictResolverTest, ResolveConflict_Files) { 296 const GURL kOrigin("chrome-extension://example"); 297 const std::string sync_root = CreateSyncRoot(); 298 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 299 InitializeMetadataDatabase(); 300 RegisterApp(kOrigin.host(), app_root); 301 RunRemoteToLocalSyncerUntilIdle(); 302 303 const std::string kTitle = "foo"; 304 const std::string primary = CreateRemoteFile(app_root, kTitle, "data1"); 305 CreateRemoteFile(app_root, kTitle, "data2"); 306 CreateRemoteFile(app_root, kTitle, "data3"); 307 CreateRemoteFile(app_root, kTitle, "data4"); 308 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 309 RunRemoteToLocalSyncerUntilIdle(); 310 311 ScopedVector<google_apis::ResourceEntry> entries = 312 GetResourceEntriesForParentAndTitle(app_root, kTitle); 313 ASSERT_EQ(4u, entries.size()); 314 315 // Only primary file should survive. 316 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver()); 317 VerifyConflictResolution(app_root, kTitle, primary, 318 google_apis::ENTRY_KIND_FILE); 319} 320 321TEST_F(ConflictResolverTest, ResolveConflict_Folders) { 322 const GURL kOrigin("chrome-extension://example"); 323 const std::string sync_root = CreateSyncRoot(); 324 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 325 InitializeMetadataDatabase(); 326 RegisterApp(kOrigin.host(), app_root); 327 RunRemoteToLocalSyncerUntilIdle(); 328 329 const std::string kTitle = "foo"; 330 const std::string primary = CreateRemoteFolder(app_root, kTitle); 331 CreateRemoteFolder(app_root, kTitle); 332 CreateRemoteFolder(app_root, kTitle); 333 CreateRemoteFolder(app_root, kTitle); 334 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 335 RunRemoteToLocalSyncerUntilIdle(); 336 337 ScopedVector<google_apis::ResourceEntry> entries = 338 GetResourceEntriesForParentAndTitle(app_root, kTitle); 339 ASSERT_EQ(4u, entries.size()); 340 341 // Only primary file should survive. 342 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver()); 343 VerifyConflictResolution(app_root, kTitle, primary, 344 google_apis::ENTRY_KIND_FOLDER); 345} 346 347TEST_F(ConflictResolverTest, ResolveConflict_FilesAndFolders) { 348 const GURL kOrigin("chrome-extension://example"); 349 const std::string sync_root = CreateSyncRoot(); 350 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 351 InitializeMetadataDatabase(); 352 RegisterApp(kOrigin.host(), app_root); 353 RunRemoteToLocalSyncerUntilIdle(); 354 355 const std::string kTitle = "foo"; 356 CreateRemoteFile(app_root, kTitle, "data"); 357 const std::string primary = CreateRemoteFolder(app_root, kTitle); 358 CreateRemoteFile(app_root, kTitle, "data2"); 359 CreateRemoteFolder(app_root, kTitle); 360 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 361 RunRemoteToLocalSyncerUntilIdle(); 362 363 ScopedVector<google_apis::ResourceEntry> entries = 364 GetResourceEntriesForParentAndTitle(app_root, kTitle); 365 ASSERT_EQ(4u, entries.size()); 366 367 // Only primary file should survive. 368 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver()); 369 VerifyConflictResolution(app_root, kTitle, primary, 370 google_apis::ENTRY_KIND_FOLDER); 371} 372 373TEST_F(ConflictResolverTest, ResolveConflict_RemoteFolderOnLocalFile) { 374 const GURL kOrigin("chrome-extension://example"); 375 const std::string sync_root = CreateSyncRoot(); 376 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 377 InitializeMetadataDatabase(); 378 RegisterApp(kOrigin.host(), app_root); 379 RunRemoteToLocalSyncerUntilIdle(); 380 381 const std::string kTitle = "foo"; 382 fileapi::FileSystemURL kURL = URL(kOrigin, kTitle); 383 384 // Create a file on local and sync it. 385 CreateLocalFile(kURL); 386 RunLocalToRemoteSyncer( 387 kURL, 388 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE)); 389 390 // Create a folder on remote and sync it. 391 const std::string primary = CreateRemoteFolder(app_root, kTitle); 392 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 393 RunRemoteToLocalSyncerUntilIdle(); 394 395 ScopedVector<google_apis::ResourceEntry> entries = 396 GetResourceEntriesForParentAndTitle(app_root, kTitle); 397 ASSERT_EQ(2u, entries.size()); 398 399 // Run conflict resolver. Only primary file should survive. 400 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver()); 401 VerifyConflictResolution(app_root, kTitle, primary, 402 google_apis::ENTRY_KIND_FOLDER); 403 404 // Continue to run remote-to-local sync. 405 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 406 RunRemoteToLocalSyncerUntilIdle(); 407 408 // Verify that the local side has been synced to the same state 409 // (i.e. file deletion and folder creation). 410 URLToFileChangesMap expected_changes; 411 expected_changes[kURL].push_back( 412 FileChange(FileChange::FILE_CHANGE_DELETE, 413 SYNC_FILE_TYPE_UNKNOWN)); 414 expected_changes[kURL].push_back( 415 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, 416 SYNC_FILE_TYPE_DIRECTORY)); 417 VerifyLocalChangeConsistency(expected_changes); 418} 419 420TEST_F(ConflictResolverTest, ResolveConflict_RemoteNestedFolderOnLocalFile) { 421 const GURL kOrigin("chrome-extension://example"); 422 const std::string sync_root = CreateSyncRoot(); 423 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 424 InitializeMetadataDatabase(); 425 RegisterApp(kOrigin.host(), app_root); 426 RunRemoteToLocalSyncerUntilIdle(); 427 428 const std::string kTitle = "foo"; 429 fileapi::FileSystemURL kURL = URL(kOrigin, kTitle); 430 431 // Create a file on local and sync it. 432 CreateLocalFile(kURL); 433 RunLocalToRemoteSyncer( 434 kURL, 435 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE)); 436 437 // Create a folder and subfolder in it on remote, and sync it. 438 const std::string primary = CreateRemoteFolder(app_root, kTitle); 439 CreateRemoteFolder(primary, "nested"); 440 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 441 RunRemoteToLocalSyncerUntilIdle(); 442 443 ScopedVector<google_apis::ResourceEntry> entries = 444 GetResourceEntriesForParentAndTitle(app_root, kTitle); 445 ASSERT_EQ(2u, entries.size()); 446 447 // Run conflict resolver. Only primary file should survive. 448 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver()); 449 VerifyConflictResolution(app_root, kTitle, primary, 450 google_apis::ENTRY_KIND_FOLDER); 451 452 // Continue to run remote-to-local sync. 453 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 454 RunRemoteToLocalSyncerUntilIdle(); 455 456 // Verify that the local side has been synced to the same state 457 // (i.e. file deletion and folders creation). 458 URLToFileChangesMap expected_changes; 459 expected_changes[kURL].push_back( 460 FileChange(FileChange::FILE_CHANGE_DELETE, 461 SYNC_FILE_TYPE_UNKNOWN)); 462 expected_changes[kURL].push_back( 463 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, 464 SYNC_FILE_TYPE_DIRECTORY)); 465 expected_changes[URL(kOrigin, "foo/nested")].push_back( 466 FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, 467 SYNC_FILE_TYPE_DIRECTORY)); 468 VerifyLocalChangeConsistency(expected_changes); 469} 470 471TEST_F(ConflictResolverTest, ResolveMultiParents_File) { 472 const GURL kOrigin("chrome-extension://example"); 473 const std::string sync_root = CreateSyncRoot(); 474 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 475 InitializeMetadataDatabase(); 476 RegisterApp(kOrigin.host(), app_root); 477 RunRemoteToLocalSyncerUntilIdle(); 478 479 const std::string primary = CreateRemoteFolder(app_root, "primary"); 480 const std::string file = CreateRemoteFile(primary, "file", "data"); 481 ASSERT_EQ(google_apis::HTTP_SUCCESS, 482 AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary1"), file)); 483 ASSERT_EQ(google_apis::HTTP_SUCCESS, 484 AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary2"), file)); 485 ASSERT_EQ(google_apis::HTTP_SUCCESS, 486 AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary3"), file)); 487 488 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 489 RunRemoteToLocalSyncerUntilIdle(); 490 491 EXPECT_EQ(4, CountParents(file)); 492 493 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver()); 494 495 EXPECT_EQ(1, CountParents(file)); 496} 497 498TEST_F(ConflictResolverTest, ResolveMultiParents_Folder) { 499 const GURL kOrigin("chrome-extension://example"); 500 const std::string sync_root = CreateSyncRoot(); 501 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 502 InitializeMetadataDatabase(); 503 RegisterApp(kOrigin.host(), app_root); 504 RunRemoteToLocalSyncerUntilIdle(); 505 506 const std::string primary = CreateRemoteFolder(app_root, "primary"); 507 const std::string file = CreateRemoteFolder(primary, "folder"); 508 ASSERT_EQ(google_apis::HTTP_SUCCESS, 509 AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary1"), file)); 510 ASSERT_EQ(google_apis::HTTP_SUCCESS, 511 AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary2"), file)); 512 ASSERT_EQ(google_apis::HTTP_SUCCESS, 513 AddFileToFolder(CreateRemoteFolder(app_root, "nonprimary3"), file)); 514 515 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 516 RunRemoteToLocalSyncerUntilIdle(); 517 518 EXPECT_EQ(4, CountParents(file)); 519 520 EXPECT_EQ(SYNC_STATUS_OK, RunConflictResolver()); 521 522 EXPECT_EQ(1, CountParents(file)); 523} 524 525} // namespace drive_backend 526} // namespace sync_file_system 527