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/remote_to_local_syncer.h" 6 7 #include <map> 8 9 #include "base/bind.h" 10 #include "base/callback.h" 11 #include "base/files/scoped_temp_dir.h" 12 #include "base/logging.h" 13 #include "base/run_loop.h" 14 #include "base/thread_task_runner_handle.h" 15 #include "chrome/browser/drive/drive_uploader.h" 16 #include "chrome/browser/drive/fake_drive_service.h" 17 #include "chrome/browser/sync_file_system/drive_backend/drive_backend_constants.h" 18 #include "chrome/browser/sync_file_system/drive_backend/drive_backend_test_util.h" 19 #include "chrome/browser/sync_file_system/drive_backend/fake_drive_service_helper.h" 20 #include "chrome/browser/sync_file_system/drive_backend/list_changes_task.h" 21 #include "chrome/browser/sync_file_system/drive_backend/metadata_database.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/drive_backend/sync_task_token.h" 26 #include "chrome/browser/sync_file_system/fake_remote_change_processor.h" 27 #include "chrome/browser/sync_file_system/sync_file_system_test_util.h" 28 #include "chrome/browser/sync_file_system/syncable_file_system_util.h" 29 #include "content/public/test/test_browser_thread_bundle.h" 30 #include "google_apis/drive/gdata_errorcode.h" 31 #include "testing/gtest/include/gtest/gtest.h" 32 #include "third_party/leveldatabase/src/helpers/memenv/memenv.h" 33 #include "third_party/leveldatabase/src/include/leveldb/env.h" 34 35 namespace sync_file_system { 36 namespace drive_backend { 37 38 namespace { 39 40 storage::FileSystemURL URL(const GURL& origin, const std::string& path) { 41 return CreateSyncableFileSystemURL( 42 origin, base::FilePath::FromUTF8Unsafe(path)); 43 } 44 45 } // namespace 46 47 class RemoteToLocalSyncerTest : public testing::Test { 48 public: 49 typedef FakeRemoteChangeProcessor::URLToFileChangesMap URLToFileChangesMap; 50 51 RemoteToLocalSyncerTest() 52 : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {} 53 virtual ~RemoteToLocalSyncerTest() {} 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<drive::FakeDriveService> 60 fake_drive_service(new drive::FakeDriveService); 61 62 scoped_ptr<drive::DriveUploaderInterface> 63 drive_uploader(new drive::DriveUploader( 64 fake_drive_service.get(), 65 base::ThreadTaskRunnerHandle::Get().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 context_->SetRemoteChangeProcessor(remote_change_processor_.get()); 79 80 RegisterSyncableFileSystem(); 81 82 sync_task_manager_.reset(new SyncTaskManager( 83 base::WeakPtr<SyncTaskManager::Client>(), 84 10 /* max_parallel_task */, 85 base::ThreadTaskRunnerHandle::Get())); 86 sync_task_manager_->Initialize(SYNC_STATUS_OK); 87 } 88 89 virtual void TearDown() OVERRIDE { 90 sync_task_manager_.reset(); 91 RevokeSyncableFileSystem(); 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 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(&RemoteToLocalSyncerTest::DidInitializeMetadataDatabase, 108 base::Unretained(this), 109 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 *status_out = status; 119 context_->SetMetadataDatabase(initializer->PassMetadataDatabase()); 120 } 121 122 123 void RegisterApp(const std::string& app_id, 124 const std::string& app_root_folder_id) { 125 SyncStatusCode status = context_->GetMetadataDatabase()->RegisterApp( 126 app_id, app_root_folder_id); 127 EXPECT_EQ(SYNC_STATUS_OK, status); 128 } 129 130 MetadataDatabase* GetMetadataDatabase() { 131 return context_->GetMetadataDatabase(); 132 } 133 134 protected: 135 std::string CreateSyncRoot() { 136 std::string sync_root_folder_id; 137 EXPECT_EQ(google_apis::HTTP_CREATED, 138 fake_drive_helper_->AddOrphanedFolder( 139 kSyncRootFolderTitle, &sync_root_folder_id)); 140 return sync_root_folder_id; 141 } 142 143 std::string CreateRemoteFolder(const std::string& parent_folder_id, 144 const std::string& title) { 145 std::string folder_id; 146 EXPECT_EQ(google_apis::HTTP_CREATED, 147 fake_drive_helper_->AddFolder( 148 parent_folder_id, title, &folder_id)); 149 return folder_id; 150 } 151 152 std::string CreateRemoteFile(const std::string& parent_folder_id, 153 const std::string& title, 154 const std::string& content) { 155 std::string file_id; 156 EXPECT_EQ(google_apis::HTTP_SUCCESS, 157 fake_drive_helper_->AddFile( 158 parent_folder_id, title, content, &file_id)); 159 return file_id; 160 } 161 162 void DeleteRemoteFile(const std::string& file_id) { 163 EXPECT_EQ(google_apis::HTTP_NO_CONTENT, 164 fake_drive_helper_->DeleteResource(file_id)); 165 } 166 167 void CreateLocalFolder(const storage::FileSystemURL& url) { 168 remote_change_processor_->UpdateLocalFileMetadata( 169 url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, 170 SYNC_FILE_TYPE_DIRECTORY)); 171 } 172 173 void CreateLocalFile(const storage::FileSystemURL& url) { 174 remote_change_processor_->UpdateLocalFileMetadata( 175 url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, 176 SYNC_FILE_TYPE_FILE)); 177 } 178 179 SyncStatusCode RunSyncer() { 180 SyncStatusCode status = SYNC_STATUS_UNKNOWN; 181 scoped_ptr<RemoteToLocalSyncer> 182 syncer(new RemoteToLocalSyncer(context_.get())); 183 syncer->RunPreflight(SyncTaskToken::CreateForTesting( 184 CreateResultReceiver(&status))); 185 base::RunLoop().RunUntilIdle(); 186 return status; 187 } 188 189 SyncStatusCode RunSyncerUntilIdle() { 190 const int kRetryLimit = 100; 191 SyncStatusCode status = SYNC_STATUS_UNKNOWN; 192 int count = 0; 193 do { 194 if (count++ > kRetryLimit) 195 return status; 196 status = RunSyncer(); 197 } while (status == SYNC_STATUS_OK || 198 status == SYNC_STATUS_RETRY); 199 return status; 200 } 201 202 SyncStatusCode RunSyncerAndPromoteUntilIdle() { 203 const int kRetryLimit = 100; 204 SyncStatusCode status = SYNC_STATUS_UNKNOWN; 205 MetadataDatabase* metadata_database = context_->GetMetadataDatabase(); 206 int count = 0; 207 do { 208 if (count++ > kRetryLimit) 209 return status; 210 status = RunSyncer(); 211 } while (status == SYNC_STATUS_OK || 212 status == SYNC_STATUS_RETRY || 213 metadata_database->PromoteDemotedTrackers()); 214 return status; 215 } 216 217 SyncStatusCode ListChanges() { 218 SyncStatusCode status = SYNC_STATUS_UNKNOWN; 219 sync_task_manager_->ScheduleSyncTask( 220 FROM_HERE, 221 scoped_ptr<SyncTask>(new ListChangesTask(context_.get())), 222 SyncTaskManager::PRIORITY_MED, 223 CreateResultReceiver(&status)); 224 base::RunLoop().RunUntilIdle(); 225 return status; 226 } 227 228 void AppendExpectedChange(const storage::FileSystemURL& url, 229 FileChange::ChangeType change_type, 230 SyncFileType file_type) { 231 expected_changes_[url].push_back(FileChange(change_type, file_type)); 232 } 233 234 void VerifyConsistency() { 235 remote_change_processor_->VerifyConsistency(expected_changes_); 236 } 237 238 private: 239 content::TestBrowserThreadBundle thread_bundle_; 240 base::ScopedTempDir database_dir_; 241 scoped_ptr<leveldb::Env> in_memory_env_; 242 243 scoped_ptr<SyncEngineContext> context_; 244 scoped_ptr<FakeDriveServiceHelper> fake_drive_helper_; 245 scoped_ptr<FakeRemoteChangeProcessor> remote_change_processor_; 246 247 scoped_ptr<SyncTaskManager> sync_task_manager_; 248 249 URLToFileChangesMap expected_changes_; 250 251 DISALLOW_COPY_AND_ASSIGN(RemoteToLocalSyncerTest); 252 }; 253 254 TEST_F(RemoteToLocalSyncerTest, AddNewFile) { 255 const GURL kOrigin("chrome-extension://example"); 256 const std::string sync_root = CreateSyncRoot(); 257 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 258 InitializeMetadataDatabase(); 259 RegisterApp(kOrigin.host(), app_root); 260 261 const std::string folder1 = CreateRemoteFolder(app_root, "folder1"); 262 const std::string file1 = CreateRemoteFile(app_root, "file1", "data1"); 263 const std::string folder2 = CreateRemoteFolder(folder1, "folder2"); 264 const std::string file2 = CreateRemoteFile(folder1, "file2", "data2"); 265 266 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerAndPromoteUntilIdle()); 267 268 // Create expected changes. 269 // TODO(nhiroki): Clean up creating URL part. 270 AppendExpectedChange(URL(kOrigin, "folder1"), 271 FileChange::FILE_CHANGE_ADD_OR_UPDATE, 272 SYNC_FILE_TYPE_DIRECTORY); 273 AppendExpectedChange(URL(kOrigin, "file1"), 274 FileChange::FILE_CHANGE_ADD_OR_UPDATE, 275 SYNC_FILE_TYPE_FILE); 276 AppendExpectedChange(URL(kOrigin, "folder1/folder2"), 277 FileChange::FILE_CHANGE_ADD_OR_UPDATE, 278 SYNC_FILE_TYPE_DIRECTORY); 279 AppendExpectedChange(URL(kOrigin, "folder1/file2"), 280 FileChange::FILE_CHANGE_ADD_OR_UPDATE, 281 SYNC_FILE_TYPE_FILE); 282 283 VerifyConsistency(); 284 285 EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker()); 286 } 287 288 TEST_F(RemoteToLocalSyncerTest, DeleteFile) { 289 const GURL kOrigin("chrome-extension://example"); 290 const std::string sync_root = CreateSyncRoot(); 291 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 292 InitializeMetadataDatabase(); 293 RegisterApp(kOrigin.host(), app_root); 294 295 const std::string folder = CreateRemoteFolder(app_root, "folder"); 296 const std::string file = CreateRemoteFile(app_root, "file", "data"); 297 298 AppendExpectedChange(URL(kOrigin, "folder"), 299 FileChange::FILE_CHANGE_ADD_OR_UPDATE, 300 SYNC_FILE_TYPE_DIRECTORY); 301 AppendExpectedChange(URL(kOrigin, "file"), 302 FileChange::FILE_CHANGE_ADD_OR_UPDATE, 303 SYNC_FILE_TYPE_FILE); 304 305 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerAndPromoteUntilIdle()); 306 VerifyConsistency(); 307 308 DeleteRemoteFile(folder); 309 DeleteRemoteFile(file); 310 311 AppendExpectedChange(URL(kOrigin, "folder"), 312 FileChange::FILE_CHANGE_DELETE, 313 SYNC_FILE_TYPE_UNKNOWN); 314 AppendExpectedChange(URL(kOrigin, "file"), 315 FileChange::FILE_CHANGE_DELETE, 316 SYNC_FILE_TYPE_UNKNOWN); 317 318 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 319 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle()); 320 VerifyConsistency(); 321 322 EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker()); 323 } 324 325 TEST_F(RemoteToLocalSyncerTest, DeleteNestedFiles) { 326 const GURL kOrigin("chrome-extension://example"); 327 const std::string sync_root = CreateSyncRoot(); 328 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 329 InitializeMetadataDatabase(); 330 RegisterApp(kOrigin.host(), app_root); 331 332 const std::string folder1 = CreateRemoteFolder(app_root, "folder1"); 333 const std::string file1 = CreateRemoteFile(app_root, "file1", "data1"); 334 const std::string folder2 = CreateRemoteFolder(folder1, "folder2"); 335 const std::string file2 = CreateRemoteFile(folder1, "file2", "data2"); 336 337 AppendExpectedChange(URL(kOrigin, "folder1"), 338 FileChange::FILE_CHANGE_ADD_OR_UPDATE, 339 SYNC_FILE_TYPE_DIRECTORY); 340 AppendExpectedChange(URL(kOrigin, "file1"), 341 FileChange::FILE_CHANGE_ADD_OR_UPDATE, 342 SYNC_FILE_TYPE_FILE); 343 AppendExpectedChange(URL(kOrigin, "folder1/folder2"), 344 FileChange::FILE_CHANGE_ADD_OR_UPDATE, 345 SYNC_FILE_TYPE_DIRECTORY); 346 AppendExpectedChange(URL(kOrigin, "folder1/file2"), 347 FileChange::FILE_CHANGE_ADD_OR_UPDATE, 348 SYNC_FILE_TYPE_FILE); 349 350 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerAndPromoteUntilIdle()); 351 VerifyConsistency(); 352 353 DeleteRemoteFile(folder1); 354 355 AppendExpectedChange(URL(kOrigin, "folder1"), 356 FileChange::FILE_CHANGE_DELETE, 357 SYNC_FILE_TYPE_UNKNOWN); 358 // Changes for descendant files ("folder2" and "file2") should be ignored. 359 360 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 361 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle()); 362 VerifyConsistency(); 363 364 EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker()); 365 } 366 367 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateFileOnFolder) { 368 const GURL kOrigin("chrome-extension://example"); 369 const std::string sync_root = CreateSyncRoot(); 370 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 371 InitializeMetadataDatabase(); 372 RegisterApp(kOrigin.host(), app_root); 373 374 CreateLocalFolder(URL(kOrigin, "folder")); 375 CreateRemoteFile(app_root, "folder", "data"); 376 377 // Folder-File conflict happens. File creation should be ignored. 378 379 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 380 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle()); 381 VerifyConsistency(); 382 383 // Tracker for the remote file should has low priority. 384 EXPECT_FALSE(GetMetadataDatabase()->GetDirtyTracker(NULL)); 385 EXPECT_TRUE(GetMetadataDatabase()->HasDemotedDirtyTracker()); 386 } 387 388 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateFolderOnFile) { 389 const GURL kOrigin("chrome-extension://example"); 390 const std::string sync_root = CreateSyncRoot(); 391 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 392 InitializeMetadataDatabase(); 393 RegisterApp(kOrigin.host(), app_root); 394 395 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle()); 396 VerifyConsistency(); 397 398 CreateLocalFile(URL(kOrigin, "file")); 399 CreateRemoteFolder(app_root, "file"); 400 401 // File-Folder conflict happens. Folder should override the existing file. 402 AppendExpectedChange(URL(kOrigin, "file"), 403 FileChange::FILE_CHANGE_ADD_OR_UPDATE, 404 SYNC_FILE_TYPE_DIRECTORY); 405 406 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 407 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle()); 408 VerifyConsistency(); 409 410 EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker()); 411 } 412 413 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateFolderOnFolder) { 414 const GURL kOrigin("chrome-extension://example"); 415 const std::string sync_root = CreateSyncRoot(); 416 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 417 InitializeMetadataDatabase(); 418 RegisterApp(kOrigin.host(), app_root); 419 420 CreateLocalFolder(URL(kOrigin, "folder")); 421 CreateRemoteFolder(app_root, "folder"); 422 423 // Folder-Folder conflict happens. Folder creation should be ignored. 424 425 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 426 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle()); 427 VerifyConsistency(); 428 429 EXPECT_FALSE(GetMetadataDatabase()->HasDirtyTracker()); 430 } 431 432 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateFileOnFile) { 433 const GURL kOrigin("chrome-extension://example"); 434 const std::string sync_root = CreateSyncRoot(); 435 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 436 InitializeMetadataDatabase(); 437 RegisterApp(kOrigin.host(), app_root); 438 439 CreateLocalFile(URL(kOrigin, "file")); 440 CreateRemoteFile(app_root, "file", "data"); 441 442 // File-File conflict happens. File creation should be ignored. 443 444 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 445 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle()); 446 VerifyConsistency(); 447 448 // Tracker for the remote file should be lowered. 449 EXPECT_FALSE(GetMetadataDatabase()->GetDirtyTracker(NULL)); 450 EXPECT_TRUE(GetMetadataDatabase()->HasDemotedDirtyTracker()); 451 } 452 453 TEST_F(RemoteToLocalSyncerTest, Conflict_CreateNestedFolderOnFile) { 454 const GURL kOrigin("chrome-extension://example"); 455 const std::string sync_root = CreateSyncRoot(); 456 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 457 InitializeMetadataDatabase(); 458 RegisterApp(kOrigin.host(), app_root); 459 460 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle()); 461 VerifyConsistency(); 462 463 const std::string folder = CreateRemoteFolder(app_root, "folder"); 464 CreateLocalFile(URL(kOrigin, "/folder")); 465 CreateRemoteFile(folder, "file", "data"); 466 467 // File-Folder conflict happens. Folder should override the existing file. 468 AppendExpectedChange(URL(kOrigin, "/folder"), 469 FileChange::FILE_CHANGE_ADD_OR_UPDATE, 470 SYNC_FILE_TYPE_DIRECTORY); 471 472 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 473 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle()); 474 VerifyConsistency(); 475 } 476 477 TEST_F(RemoteToLocalSyncerTest, AppRootDeletion) { 478 const GURL kOrigin("chrome-extension://example"); 479 const std::string sync_root = CreateSyncRoot(); 480 const std::string app_root = CreateRemoteFolder(sync_root, kOrigin.host()); 481 InitializeMetadataDatabase(); 482 RegisterApp(kOrigin.host(), app_root); 483 484 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle()); 485 VerifyConsistency(); 486 487 DeleteRemoteFile(app_root); 488 489 AppendExpectedChange(URL(kOrigin, "/"), 490 FileChange::FILE_CHANGE_DELETE, 491 SYNC_FILE_TYPE_UNKNOWN); 492 493 EXPECT_EQ(SYNC_STATUS_OK, ListChanges()); 494 EXPECT_EQ(SYNC_STATUS_NO_CHANGE_TO_SYNC, RunSyncerUntilIdle()); 495 VerifyConsistency(); 496 497 // SyncEngine will re-register the app and resurrect the app root later. 498 } 499 500 } // namespace drive_backend 501 } // namespace sync_file_system 502