| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/sync_file_system/local/local_file_sync_context.h" |
| |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/file_util.h" |
| #include "base/files/file_path.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/platform_file.h" |
| #include "base/stl_util.h" |
| #include "chrome/browser/sync_file_system/local/canned_syncable_file_system.h" |
| #include "chrome/browser/sync_file_system/local/local_file_change_tracker.h" |
| #include "chrome/browser/sync_file_system/local/sync_file_system_backend.h" |
| #include "chrome/browser/sync_file_system/sync_file_metadata.h" |
| #include "chrome/browser/sync_file_system/sync_status_code.h" |
| #include "chrome/browser/sync_file_system/syncable_file_system_util.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "webkit/browser/blob/mock_blob_url_request_context.h" |
| #include "webkit/browser/fileapi/file_system_context.h" |
| #include "webkit/browser/fileapi/file_system_operation_runner.h" |
| #include "webkit/browser/fileapi/isolated_context.h" |
| #include "webkit/common/blob/scoped_file.h" |
| |
| #define FPL FILE_PATH_LITERAL |
| |
| using content::BrowserThread; |
| using fileapi::FileSystemContext; |
| using fileapi::FileSystemURL; |
| using fileapi::FileSystemURLSet; |
| |
| // This tests LocalFileSyncContext behavior in multi-thread / |
| // multi-file-system-context environment. |
| // Basic combined tests (single-thread / single-file-system-context) |
| // that involve LocalFileSyncContext are also in |
| // syncable_file_system_unittests.cc. |
| |
| namespace sync_file_system { |
| |
| namespace { |
| const char kOrigin1[] = "http://example.com"; |
| const char kOrigin2[] = "http://chromium.org"; |
| } |
| |
| class LocalFileSyncContextTest : public testing::Test { |
| protected: |
| LocalFileSyncContextTest() |
| : thread_bundle_( |
| content::TestBrowserThreadBundle::REAL_FILE_THREAD | |
| content::TestBrowserThreadBundle::REAL_IO_THREAD), |
| status_(SYNC_FILE_ERROR_FAILED), |
| file_error_(base::PLATFORM_FILE_ERROR_FAILED), |
| async_modify_finished_(false), |
| has_inflight_prepare_for_sync_(false) {} |
| |
| virtual void SetUp() OVERRIDE { |
| RegisterSyncableFileSystem(); |
| ASSERT_TRUE(dir_.CreateUniqueTempDir()); |
| |
| ui_task_runner_ = base::MessageLoop::current()->message_loop_proxy(); |
| io_task_runner_ = BrowserThread::GetMessageLoopProxyForThread( |
| BrowserThread::IO); |
| file_task_runner_ = BrowserThread::GetMessageLoopProxyForThread( |
| BrowserThread::IO); |
| } |
| |
| virtual void TearDown() OVERRIDE { |
| RevokeSyncableFileSystem(); |
| } |
| |
| void StartPrepareForSync(FileSystemContext* file_system_context, |
| const FileSystemURL& url, |
| LocalFileSyncContext::SyncMode sync_mode, |
| SyncFileMetadata* metadata, |
| FileChangeList* changes, |
| webkit_blob::ScopedFile* snapshot) { |
| ASSERT_TRUE(changes != NULL); |
| ASSERT_FALSE(has_inflight_prepare_for_sync_); |
| status_ = SYNC_STATUS_UNKNOWN; |
| has_inflight_prepare_for_sync_ = true; |
| sync_context_->PrepareForSync( |
| file_system_context, |
| url, |
| sync_mode, |
| base::Bind(&LocalFileSyncContextTest::DidPrepareForSync, |
| base::Unretained(this), metadata, changes, snapshot)); |
| } |
| |
| SyncStatusCode PrepareForSync(FileSystemContext* file_system_context, |
| const FileSystemURL& url, |
| LocalFileSyncContext::SyncMode sync_mode, |
| SyncFileMetadata* metadata, |
| FileChangeList* changes, |
| webkit_blob::ScopedFile* snapshot) { |
| StartPrepareForSync(file_system_context, url, sync_mode, |
| metadata, changes, snapshot); |
| base::MessageLoop::current()->Run(); |
| return status_; |
| } |
| |
| base::Closure GetPrepareForSyncClosure( |
| FileSystemContext* file_system_context, |
| const FileSystemURL& url, |
| LocalFileSyncContext::SyncMode sync_mode, |
| SyncFileMetadata* metadata, |
| FileChangeList* changes, |
| webkit_blob::ScopedFile* snapshot) { |
| return base::Bind(&LocalFileSyncContextTest::StartPrepareForSync, |
| base::Unretained(this), |
| base::Unretained(file_system_context), |
| url, sync_mode, metadata, changes, snapshot); |
| } |
| |
| void DidPrepareForSync(SyncFileMetadata* metadata_out, |
| FileChangeList* changes_out, |
| webkit_blob::ScopedFile* snapshot_out, |
| SyncStatusCode status, |
| const LocalFileSyncInfo& sync_file_info, |
| webkit_blob::ScopedFile snapshot) { |
| ASSERT_TRUE(ui_task_runner_->RunsTasksOnCurrentThread()); |
| has_inflight_prepare_for_sync_ = false; |
| status_ = status; |
| *metadata_out = sync_file_info.metadata; |
| *changes_out = sync_file_info.changes; |
| if (snapshot_out) |
| *snapshot_out = snapshot.Pass(); |
| base::MessageLoop::current()->Quit(); |
| } |
| |
| SyncStatusCode ApplyRemoteChange(FileSystemContext* file_system_context, |
| const FileChange& change, |
| const base::FilePath& local_path, |
| const FileSystemURL& url, |
| SyncFileType expected_file_type) { |
| SCOPED_TRACE(testing::Message() << "ApplyChange for " << |
| url.DebugString()); |
| |
| // First we should call PrepareForSync to disable writing. |
| SyncFileMetadata metadata; |
| FileChangeList changes; |
| EXPECT_EQ(SYNC_STATUS_OK, |
| PrepareForSync(file_system_context, url, |
| LocalFileSyncContext::SYNC_EXCLUSIVE, |
| &metadata, &changes, NULL)); |
| EXPECT_EQ(expected_file_type, metadata.file_type); |
| |
| status_ = SYNC_STATUS_UNKNOWN; |
| sync_context_->ApplyRemoteChange( |
| file_system_context, change, local_path, url, |
| base::Bind(&LocalFileSyncContextTest::DidApplyRemoteChange, |
| base::Unretained(this), |
| make_scoped_refptr(file_system_context), url)); |
| base::MessageLoop::current()->Run(); |
| return status_; |
| } |
| |
| void DidApplyRemoteChange(FileSystemContext* file_system_context, |
| const FileSystemURL& url, |
| SyncStatusCode status) { |
| status_ = status; |
| sync_context_->FinalizeExclusiveSync( |
| file_system_context, url, |
| status == SYNC_STATUS_OK /* clear_local_changes */, |
| base::MessageLoop::QuitClosure()); |
| } |
| |
| void StartModifyFileOnIOThread(CannedSyncableFileSystem* file_system, |
| const FileSystemURL& url) { |
| ASSERT_TRUE(file_system != NULL); |
| if (!io_task_runner_->RunsTasksOnCurrentThread()) { |
| async_modify_finished_ = false; |
| ASSERT_TRUE(ui_task_runner_->RunsTasksOnCurrentThread()); |
| io_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&LocalFileSyncContextTest::StartModifyFileOnIOThread, |
| base::Unretained(this), file_system, url)); |
| return; |
| } |
| ASSERT_TRUE(io_task_runner_->RunsTasksOnCurrentThread()); |
| file_error_ = base::PLATFORM_FILE_ERROR_FAILED; |
| file_system->operation_runner()->Truncate( |
| url, 1, base::Bind(&LocalFileSyncContextTest::DidModifyFile, |
| base::Unretained(this))); |
| } |
| |
| base::PlatformFileError WaitUntilModifyFileIsDone() { |
| while (!async_modify_finished_) |
| base::MessageLoop::current()->RunUntilIdle(); |
| return file_error_; |
| } |
| |
| void DidModifyFile(base::PlatformFileError error) { |
| if (!ui_task_runner_->RunsTasksOnCurrentThread()) { |
| ASSERT_TRUE(io_task_runner_->RunsTasksOnCurrentThread()); |
| ui_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&LocalFileSyncContextTest::DidModifyFile, |
| base::Unretained(this), error)); |
| return; |
| } |
| ASSERT_TRUE(ui_task_runner_->RunsTasksOnCurrentThread()); |
| file_error_ = error; |
| async_modify_finished_ = true; |
| } |
| |
| void SimulateFinishSync(FileSystemContext* file_system_context, |
| const FileSystemURL& url, |
| SyncStatusCode status, |
| LocalFileSyncContext::SyncMode sync_mode) { |
| if (sync_mode == LocalFileSyncContext::SYNC_SNAPSHOT) { |
| sync_context_->FinalizeSnapshotSync( |
| file_system_context, url, status, |
| base::Bind(&base::DoNothing)); |
| } else { |
| sync_context_->FinalizeExclusiveSync( |
| file_system_context, url, |
| status == SYNC_STATUS_OK /* clear_local_changes */, |
| base::Bind(&base::DoNothing)); |
| } |
| } |
| |
| void PrepareForSync_Basic(LocalFileSyncContext::SyncMode sync_mode, |
| SyncStatusCode simulate_sync_finish_status) { |
| CannedSyncableFileSystem file_system(GURL(kOrigin1), |
| io_task_runner_.get(), |
| file_task_runner_.get()); |
| file_system.SetUp(); |
| sync_context_ = new LocalFileSyncContext( |
| dir_.path(), ui_task_runner_.get(), io_task_runner_.get()); |
| ASSERT_EQ(SYNC_STATUS_OK, |
| file_system.MaybeInitializeFileSystemContext( |
| sync_context_.get())); |
| ASSERT_EQ(base::PLATFORM_FILE_OK, file_system.OpenFileSystem()); |
| |
| const FileSystemURL kFile(file_system.URL("file")); |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateFile(kFile)); |
| |
| SyncFileMetadata metadata; |
| FileChangeList changes; |
| EXPECT_EQ(SYNC_STATUS_OK, |
| PrepareForSync(file_system.file_system_context(), kFile, |
| sync_mode, &metadata, &changes, NULL)); |
| EXPECT_EQ(1U, changes.size()); |
| EXPECT_TRUE(changes.list().back().IsFile()); |
| EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); |
| |
| // We should see the same set of changes. |
| file_system.GetChangesForURLInTracker(kFile, &changes); |
| EXPECT_EQ(1U, changes.size()); |
| EXPECT_TRUE(changes.list().back().IsFile()); |
| EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); |
| |
| SimulateFinishSync(file_system.file_system_context(), kFile, |
| simulate_sync_finish_status, sync_mode); |
| |
| file_system.GetChangesForURLInTracker(kFile, &changes); |
| if (simulate_sync_finish_status == SYNC_STATUS_OK) { |
| // The change's cleared. |
| EXPECT_TRUE(changes.empty()); |
| } else { |
| EXPECT_EQ(1U, changes.size()); |
| EXPECT_TRUE(changes.list().back().IsFile()); |
| EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); |
| } |
| |
| sync_context_->ShutdownOnUIThread(); |
| sync_context_ = NULL; |
| |
| file_system.TearDown(); |
| } |
| |
| void PrepareForSync_WriteDuringSync( |
| LocalFileSyncContext::SyncMode sync_mode) { |
| CannedSyncableFileSystem file_system(GURL(kOrigin1), |
| io_task_runner_.get(), |
| file_task_runner_.get()); |
| file_system.SetUp(); |
| sync_context_ = new LocalFileSyncContext( |
| dir_.path(), ui_task_runner_.get(), io_task_runner_.get()); |
| ASSERT_EQ(SYNC_STATUS_OK, |
| file_system.MaybeInitializeFileSystemContext( |
| sync_context_.get())); |
| ASSERT_EQ(base::PLATFORM_FILE_OK, file_system.OpenFileSystem()); |
| |
| const FileSystemURL kFile(file_system.URL("file")); |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateFile(kFile)); |
| |
| SyncFileMetadata metadata; |
| FileChangeList changes; |
| webkit_blob::ScopedFile snapshot; |
| EXPECT_EQ(SYNC_STATUS_OK, |
| PrepareForSync(file_system.file_system_context(), kFile, |
| sync_mode, &metadata, &changes, &snapshot)); |
| EXPECT_EQ(1U, changes.size()); |
| EXPECT_TRUE(changes.list().back().IsFile()); |
| EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); |
| |
| EXPECT_EQ(sync_mode == LocalFileSyncContext::SYNC_SNAPSHOT, |
| !snapshot.path().empty()); |
| |
| // Tracker keeps same set of changes. |
| file_system.GetChangesForURLInTracker(kFile, &changes); |
| EXPECT_EQ(1U, changes.size()); |
| EXPECT_TRUE(changes.list().back().IsFile()); |
| EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); |
| |
| StartModifyFileOnIOThread(&file_system, kFile); |
| |
| if (sync_mode == LocalFileSyncContext::SYNC_SNAPSHOT) { |
| // Write should succeed. |
| EXPECT_EQ(base::PLATFORM_FILE_OK, WaitUntilModifyFileIsDone()); |
| } else { |
| base::MessageLoop::current()->RunUntilIdle(); |
| EXPECT_FALSE(async_modify_finished_); |
| } |
| |
| SimulateFinishSync(file_system.file_system_context(), kFile, |
| SYNC_STATUS_OK, sync_mode); |
| |
| EXPECT_EQ(base::PLATFORM_FILE_OK, WaitUntilModifyFileIsDone()); |
| |
| // Sync succeeded, but the other change that was made during or |
| // after sync is recorded. |
| file_system.GetChangesForURLInTracker(kFile, &changes); |
| EXPECT_EQ(1U, changes.size()); |
| EXPECT_TRUE(changes.list().back().IsFile()); |
| EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); |
| |
| sync_context_->ShutdownOnUIThread(); |
| sync_context_ = NULL; |
| |
| file_system.TearDown(); |
| } |
| |
| ScopedEnableSyncFSDirectoryOperation enable_directory_operation_; |
| |
| base::ScopedTempDir dir_; |
| |
| // These need to remain until the very end. |
| content::TestBrowserThreadBundle thread_bundle_; |
| |
| scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; |
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_; |
| scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_; |
| |
| scoped_refptr<LocalFileSyncContext> sync_context_; |
| |
| SyncStatusCode status_; |
| base::PlatformFileError file_error_; |
| bool async_modify_finished_; |
| bool has_inflight_prepare_for_sync_; |
| }; |
| |
| TEST_F(LocalFileSyncContextTest, ConstructAndDestruct) { |
| sync_context_ = |
| new LocalFileSyncContext( |
| dir_.path(), ui_task_runner_.get(), io_task_runner_.get()); |
| sync_context_->ShutdownOnUIThread(); |
| } |
| |
| TEST_F(LocalFileSyncContextTest, InitializeFileSystemContext) { |
| CannedSyncableFileSystem file_system(GURL(kOrigin1), |
| io_task_runner_.get(), |
| file_task_runner_.get()); |
| file_system.SetUp(); |
| |
| sync_context_ = new LocalFileSyncContext( |
| dir_.path(), ui_task_runner_.get(), io_task_runner_.get()); |
| |
| // Initializes file_system using |sync_context_|. |
| EXPECT_EQ(SYNC_STATUS_OK, |
| file_system.MaybeInitializeFileSystemContext(sync_context_.get())); |
| |
| // Make sure everything's set up for file_system to be able to handle |
| // syncable file system operations. |
| EXPECT_TRUE(file_system.backend()->sync_context() != NULL); |
| EXPECT_TRUE(file_system.backend()->change_tracker() != NULL); |
| EXPECT_EQ(sync_context_.get(), file_system.backend()->sync_context()); |
| |
| // Calling MaybeInitialize for the same context multiple times must be ok. |
| EXPECT_EQ(SYNC_STATUS_OK, |
| file_system.MaybeInitializeFileSystemContext(sync_context_.get())); |
| EXPECT_EQ(sync_context_.get(), file_system.backend()->sync_context()); |
| |
| // Opens the file_system, perform some operation and see if the change tracker |
| // correctly captures the change. |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.OpenFileSystem()); |
| |
| const FileSystemURL kURL(file_system.URL("foo")); |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateFile(kURL)); |
| |
| FileSystemURLSet urls; |
| file_system.GetChangedURLsInTracker(&urls); |
| ASSERT_EQ(1U, urls.size()); |
| EXPECT_TRUE(ContainsKey(urls, kURL)); |
| |
| // Finishing the test. |
| sync_context_->ShutdownOnUIThread(); |
| file_system.TearDown(); |
| } |
| |
| TEST_F(LocalFileSyncContextTest, MultipleFileSystemContexts) { |
| CannedSyncableFileSystem file_system1(GURL(kOrigin1), |
| io_task_runner_.get(), |
| file_task_runner_.get()); |
| CannedSyncableFileSystem file_system2(GURL(kOrigin2), |
| io_task_runner_.get(), |
| file_task_runner_.get()); |
| file_system1.SetUp(); |
| file_system2.SetUp(); |
| |
| sync_context_ = new LocalFileSyncContext( |
| dir_.path(), ui_task_runner_.get(), io_task_runner_.get()); |
| |
| // Initializes file_system1 and file_system2. |
| EXPECT_EQ(SYNC_STATUS_OK, |
| file_system1.MaybeInitializeFileSystemContext(sync_context_.get())); |
| EXPECT_EQ(SYNC_STATUS_OK, |
| file_system2.MaybeInitializeFileSystemContext(sync_context_.get())); |
| |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system1.OpenFileSystem()); |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system2.OpenFileSystem()); |
| |
| const FileSystemURL kURL1(file_system1.URL("foo")); |
| const FileSystemURL kURL2(file_system2.URL("bar")); |
| |
| // Creates a file in file_system1. |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system1.CreateFile(kURL1)); |
| |
| // file_system1's tracker must have recorded the change. |
| FileSystemURLSet urls; |
| file_system1.GetChangedURLsInTracker(&urls); |
| ASSERT_EQ(1U, urls.size()); |
| EXPECT_TRUE(ContainsKey(urls, kURL1)); |
| |
| // file_system1's tracker must have no change. |
| urls.clear(); |
| file_system2.GetChangedURLsInTracker(&urls); |
| ASSERT_TRUE(urls.empty()); |
| |
| // Creates a directory in file_system2. |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system2.CreateDirectory(kURL2)); |
| |
| // file_system1's tracker must have the change for kURL1 as before. |
| urls.clear(); |
| file_system1.GetChangedURLsInTracker(&urls); |
| ASSERT_EQ(1U, urls.size()); |
| EXPECT_TRUE(ContainsKey(urls, kURL1)); |
| |
| // file_system2's tracker now must have the change for kURL2. |
| urls.clear(); |
| file_system2.GetChangedURLsInTracker(&urls); |
| ASSERT_EQ(1U, urls.size()); |
| EXPECT_TRUE(ContainsKey(urls, kURL2)); |
| |
| SyncFileMetadata metadata; |
| FileChangeList changes; |
| EXPECT_EQ(SYNC_STATUS_OK, |
| PrepareForSync(file_system1.file_system_context(), kURL1, |
| LocalFileSyncContext::SYNC_EXCLUSIVE, |
| &metadata, &changes, NULL)); |
| EXPECT_EQ(1U, changes.size()); |
| EXPECT_TRUE(changes.list().back().IsFile()); |
| EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); |
| EXPECT_EQ(SYNC_FILE_TYPE_FILE, metadata.file_type); |
| EXPECT_EQ(0, metadata.size); |
| |
| changes.clear(); |
| EXPECT_EQ(SYNC_STATUS_OK, |
| PrepareForSync(file_system2.file_system_context(), kURL2, |
| LocalFileSyncContext::SYNC_EXCLUSIVE, |
| &metadata, &changes, NULL)); |
| EXPECT_EQ(1U, changes.size()); |
| EXPECT_FALSE(changes.list().back().IsFile()); |
| EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); |
| EXPECT_EQ(SYNC_FILE_TYPE_DIRECTORY, metadata.file_type); |
| EXPECT_EQ(0, metadata.size); |
| |
| sync_context_->ShutdownOnUIThread(); |
| sync_context_ = NULL; |
| |
| file_system1.TearDown(); |
| file_system2.TearDown(); |
| } |
| |
| TEST_F(LocalFileSyncContextTest, PrepareSync_SyncSuccess_Exclusive) { |
| PrepareForSync_Basic(LocalFileSyncContext::SYNC_EXCLUSIVE, |
| SYNC_STATUS_OK); |
| } |
| |
| TEST_F(LocalFileSyncContextTest, PrepareSync_SyncSuccess_Snapshot) { |
| PrepareForSync_Basic(LocalFileSyncContext::SYNC_SNAPSHOT, |
| SYNC_STATUS_OK); |
| } |
| |
| TEST_F(LocalFileSyncContextTest, PrepareSync_SyncFailure_Exclusive) { |
| PrepareForSync_Basic(LocalFileSyncContext::SYNC_EXCLUSIVE, |
| SYNC_STATUS_FAILED); |
| } |
| |
| TEST_F(LocalFileSyncContextTest, PrepareSync_SyncFailure_Snapshot) { |
| PrepareForSync_Basic(LocalFileSyncContext::SYNC_SNAPSHOT, |
| SYNC_STATUS_FAILED); |
| } |
| |
| TEST_F(LocalFileSyncContextTest, PrepareSync_WriteDuringSync_Exclusive) { |
| PrepareForSync_WriteDuringSync(LocalFileSyncContext::SYNC_EXCLUSIVE); |
| } |
| |
| TEST_F(LocalFileSyncContextTest, PrepareSync_WriteDuringSync_Snapshot) { |
| PrepareForSync_WriteDuringSync(LocalFileSyncContext::SYNC_SNAPSHOT); |
| } |
| |
| // LocalFileSyncContextTest.PrepareSyncWhileWriting is flaky on android. |
| // http://crbug.com/239793 |
| // It is also flaky on the TSAN v2 bots, and hangs other bots. |
| // http://crbug.com/305905. |
| TEST_F(LocalFileSyncContextTest, DISABLED_PrepareSyncWhileWriting) { |
| CannedSyncableFileSystem file_system(GURL(kOrigin1), |
| io_task_runner_.get(), |
| file_task_runner_.get()); |
| file_system.SetUp(); |
| sync_context_ = new LocalFileSyncContext( |
| dir_.path(), ui_task_runner_.get(), io_task_runner_.get()); |
| EXPECT_EQ(SYNC_STATUS_OK, |
| file_system.MaybeInitializeFileSystemContext(sync_context_.get())); |
| |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.OpenFileSystem()); |
| |
| const FileSystemURL kURL1(file_system.URL("foo")); |
| |
| // Creates a file in file_system. |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateFile(kURL1)); |
| |
| // Kick file write on IO thread. |
| StartModifyFileOnIOThread(&file_system, kURL1); |
| |
| // Until the operation finishes PrepareForSync should return BUSY error. |
| SyncFileMetadata metadata; |
| metadata.file_type = SYNC_FILE_TYPE_UNKNOWN; |
| FileChangeList changes; |
| EXPECT_EQ(SYNC_STATUS_FILE_BUSY, |
| PrepareForSync(file_system.file_system_context(), kURL1, |
| LocalFileSyncContext::SYNC_EXCLUSIVE, |
| &metadata, &changes, NULL)); |
| EXPECT_EQ(SYNC_FILE_TYPE_FILE, metadata.file_type); |
| |
| // Register PrepareForSync method to be invoked when kURL1 becomes |
| // syncable. (Actually this may be done after all operations are done |
| // on IO thread in this test.) |
| metadata.file_type = SYNC_FILE_TYPE_UNKNOWN; |
| changes.clear(); |
| sync_context_->RegisterURLForWaitingSync( |
| kURL1, GetPrepareForSyncClosure(file_system.file_system_context(), kURL1, |
| LocalFileSyncContext::SYNC_EXCLUSIVE, |
| &metadata, &changes, NULL)); |
| |
| // Wait for the completion. |
| EXPECT_EQ(base::PLATFORM_FILE_OK, WaitUntilModifyFileIsDone()); |
| |
| // The PrepareForSync must have been started; wait until DidPrepareForSync |
| // is done. |
| base::MessageLoop::current()->Run(); |
| ASSERT_FALSE(has_inflight_prepare_for_sync_); |
| |
| // Now PrepareForSync should have run and returned OK. |
| EXPECT_EQ(SYNC_STATUS_OK, status_); |
| EXPECT_EQ(1U, changes.size()); |
| EXPECT_TRUE(changes.list().back().IsFile()); |
| EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); |
| EXPECT_EQ(SYNC_FILE_TYPE_FILE, metadata.file_type); |
| EXPECT_EQ(1, metadata.size); |
| |
| sync_context_->ShutdownOnUIThread(); |
| sync_context_ = NULL; |
| file_system.TearDown(); |
| } |
| |
| TEST_F(LocalFileSyncContextTest, ApplyRemoteChangeForDeletion) { |
| CannedSyncableFileSystem file_system(GURL(kOrigin1), |
| io_task_runner_.get(), |
| file_task_runner_.get()); |
| file_system.SetUp(); |
| |
| sync_context_ = new LocalFileSyncContext( |
| dir_.path(), ui_task_runner_.get(), io_task_runner_.get()); |
| ASSERT_EQ(SYNC_STATUS_OK, |
| file_system.MaybeInitializeFileSystemContext(sync_context_.get())); |
| ASSERT_EQ(base::PLATFORM_FILE_OK, file_system.OpenFileSystem()); |
| |
| // Record the initial usage (likely 0). |
| int64 initial_usage = -1; |
| int64 quota = -1; |
| EXPECT_EQ(quota::kQuotaStatusOk, |
| file_system.GetUsageAndQuota(&initial_usage, "a)); |
| |
| // Create a file and directory in the file_system. |
| const FileSystemURL kFile(file_system.URL("file")); |
| const FileSystemURL kDir(file_system.URL("dir")); |
| const FileSystemURL kChild(file_system.URL("dir/child")); |
| |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateFile(kFile)); |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateDirectory(kDir)); |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateFile(kChild)); |
| |
| // file_system's change tracker must have recorded the creation. |
| FileSystemURLSet urls; |
| file_system.GetChangedURLsInTracker(&urls); |
| ASSERT_EQ(3U, urls.size()); |
| ASSERT_TRUE(ContainsKey(urls, kFile)); |
| ASSERT_TRUE(ContainsKey(urls, kDir)); |
| ASSERT_TRUE(ContainsKey(urls, kChild)); |
| for (FileSystemURLSet::iterator iter = urls.begin(); |
| iter != urls.end(); ++iter) { |
| file_system.ClearChangeForURLInTracker(*iter); |
| } |
| |
| // At this point the usage must be greater than the initial usage. |
| int64 new_usage = -1; |
| EXPECT_EQ(quota::kQuotaStatusOk, |
| file_system.GetUsageAndQuota(&new_usage, "a)); |
| EXPECT_GT(new_usage, initial_usage); |
| |
| // Now let's apply remote deletion changes. |
| FileChange change(FileChange::FILE_CHANGE_DELETE, |
| SYNC_FILE_TYPE_FILE); |
| EXPECT_EQ(SYNC_STATUS_OK, |
| ApplyRemoteChange(file_system.file_system_context(), |
| change, base::FilePath(), kFile, |
| SYNC_FILE_TYPE_FILE)); |
| |
| // The implementation doesn't check file type for deletion, and it must be ok |
| // even if we don't know if the deletion change was for a file or a directory. |
| change = FileChange(FileChange::FILE_CHANGE_DELETE, |
| SYNC_FILE_TYPE_UNKNOWN); |
| EXPECT_EQ(SYNC_STATUS_OK, |
| ApplyRemoteChange(file_system.file_system_context(), |
| change, base::FilePath(), kDir, |
| SYNC_FILE_TYPE_DIRECTORY)); |
| |
| // Check the directory/files are deleted successfully. |
| EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, |
| file_system.FileExists(kFile)); |
| EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, |
| file_system.DirectoryExists(kDir)); |
| EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, |
| file_system.FileExists(kChild)); |
| |
| // The changes applied by ApplyRemoteChange should not be recorded in |
| // the change tracker. |
| urls.clear(); |
| file_system.GetChangedURLsInTracker(&urls); |
| EXPECT_TRUE(urls.empty()); |
| |
| // The quota usage data must have reflected the deletion. |
| EXPECT_EQ(quota::kQuotaStatusOk, |
| file_system.GetUsageAndQuota(&new_usage, "a)); |
| EXPECT_EQ(new_usage, initial_usage); |
| |
| sync_context_->ShutdownOnUIThread(); |
| sync_context_ = NULL; |
| file_system.TearDown(); |
| } |
| |
| TEST_F(LocalFileSyncContextTest, ApplyRemoteChangeForAddOrUpdate) { |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| |
| CannedSyncableFileSystem file_system(GURL(kOrigin1), |
| io_task_runner_.get(), |
| file_task_runner_.get()); |
| file_system.SetUp(); |
| |
| sync_context_ = new LocalFileSyncContext( |
| dir_.path(), ui_task_runner_.get(), io_task_runner_.get()); |
| ASSERT_EQ(SYNC_STATUS_OK, |
| file_system.MaybeInitializeFileSystemContext(sync_context_.get())); |
| ASSERT_EQ(base::PLATFORM_FILE_OK, file_system.OpenFileSystem()); |
| |
| const FileSystemURL kFile1(file_system.URL("file1")); |
| const FileSystemURL kFile2(file_system.URL("file2")); |
| const FileSystemURL kDir(file_system.URL("dir")); |
| |
| const char kTestFileData0[] = "0123456789"; |
| const char kTestFileData1[] = "Lorem ipsum!"; |
| const char kTestFileData2[] = "This is sample test data."; |
| |
| // Create kFile1 and populate it with kTestFileData0. |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateFile(kFile1)); |
| EXPECT_EQ(static_cast<int64>(arraysize(kTestFileData0) - 1), |
| file_system.WriteString(kFile1, kTestFileData0)); |
| |
| // kFile2 and kDir are not there yet. |
| EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, |
| file_system.FileExists(kFile2)); |
| EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, |
| file_system.DirectoryExists(kDir)); |
| |
| // file_system's change tracker must have recorded the creation. |
| FileSystemURLSet urls; |
| file_system.GetChangedURLsInTracker(&urls); |
| ASSERT_EQ(1U, urls.size()); |
| EXPECT_TRUE(ContainsKey(urls, kFile1)); |
| file_system.ClearChangeForURLInTracker(*urls.begin()); |
| |
| // Prepare temporary files which represent the remote file data. |
| const base::FilePath kFilePath1(temp_dir.path().Append(FPL("file1"))); |
| const base::FilePath kFilePath2(temp_dir.path().Append(FPL("file2"))); |
| |
| ASSERT_EQ(static_cast<int>(arraysize(kTestFileData1) - 1), |
| file_util::WriteFile(kFilePath1, kTestFileData1, |
| arraysize(kTestFileData1) - 1)); |
| ASSERT_EQ(static_cast<int>(arraysize(kTestFileData2) - 1), |
| file_util::WriteFile(kFilePath2, kTestFileData2, |
| arraysize(kTestFileData2) - 1)); |
| |
| // Record the usage. |
| int64 usage = -1, new_usage = -1; |
| int64 quota = -1; |
| EXPECT_EQ(quota::kQuotaStatusOk, |
| file_system.GetUsageAndQuota(&usage, "a)); |
| |
| // Here in the local filesystem we have: |
| // * kFile1 with kTestFileData0 |
| // |
| // In the remote side let's assume we have: |
| // * kFile1 with kTestFileData1 |
| // * kFile2 with kTestFileData2 |
| // * kDir |
| // |
| // By calling ApplyChange's: |
| // * kFile1 will be updated to have kTestFileData1 |
| // * kFile2 will be created |
| // * kDir will be created |
| |
| // Apply the remote change to kFile1 (which will update the file). |
| FileChange change(FileChange::FILE_CHANGE_ADD_OR_UPDATE, |
| SYNC_FILE_TYPE_FILE); |
| EXPECT_EQ(SYNC_STATUS_OK, |
| ApplyRemoteChange(file_system.file_system_context(), |
| change, kFilePath1, kFile1, |
| SYNC_FILE_TYPE_FILE)); |
| |
| // Check if the usage has been increased by (kTestFileData1 - kTestFileData0). |
| const int updated_size = |
| arraysize(kTestFileData1) - arraysize(kTestFileData0); |
| EXPECT_EQ(quota::kQuotaStatusOk, |
| file_system.GetUsageAndQuota(&new_usage, "a)); |
| EXPECT_EQ(updated_size, new_usage - usage); |
| |
| // Apply remote changes to kFile2 and kDir (should create a file and |
| // directory respectively). |
| // They are non-existent yet so their expected file type (the last |
| // parameter of ApplyRemoteChange) are |
| // SYNC_FILE_TYPE_UNKNOWN. |
| change = FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, |
| SYNC_FILE_TYPE_FILE); |
| EXPECT_EQ(SYNC_STATUS_OK, |
| ApplyRemoteChange(file_system.file_system_context(), |
| change, kFilePath2, kFile2, |
| SYNC_FILE_TYPE_UNKNOWN)); |
| |
| change = FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, |
| SYNC_FILE_TYPE_DIRECTORY); |
| EXPECT_EQ(SYNC_STATUS_OK, |
| ApplyRemoteChange(file_system.file_system_context(), |
| change, base::FilePath(), kDir, |
| SYNC_FILE_TYPE_UNKNOWN)); |
| |
| // Calling ApplyRemoteChange with different file type should be handled as |
| // overwrite. |
| change = |
| FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE); |
| EXPECT_EQ(SYNC_STATUS_OK, |
| ApplyRemoteChange(file_system.file_system_context(), |
| change, |
| kFilePath1, |
| kDir, |
| SYNC_FILE_TYPE_DIRECTORY)); |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.FileExists(kDir)); |
| |
| change = FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, |
| SYNC_FILE_TYPE_DIRECTORY); |
| EXPECT_EQ(SYNC_STATUS_OK, |
| ApplyRemoteChange(file_system.file_system_context(), |
| change, |
| kFilePath1, |
| kDir, |
| SYNC_FILE_TYPE_FILE)); |
| |
| // Creating a file/directory must have increased the usage more than |
| // the size of kTestFileData2. |
| new_usage = usage; |
| EXPECT_EQ(quota::kQuotaStatusOk, |
| file_system.GetUsageAndQuota(&new_usage, "a)); |
| EXPECT_GT(new_usage, |
| static_cast<int64>(usage + arraysize(kTestFileData2) - 1)); |
| |
| // The changes applied by ApplyRemoteChange should not be recorded in |
| // the change tracker. |
| urls.clear(); |
| file_system.GetChangedURLsInTracker(&urls); |
| EXPECT_TRUE(urls.empty()); |
| |
| // Make sure all three files/directory exist. |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.FileExists(kFile1)); |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.FileExists(kFile2)); |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.DirectoryExists(kDir)); |
| |
| sync_context_->ShutdownOnUIThread(); |
| file_system.TearDown(); |
| } |
| |
| TEST_F(LocalFileSyncContextTest, ApplyRemoteChangeForAddOrUpdate_NoParent) { |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| |
| CannedSyncableFileSystem file_system(GURL(kOrigin1), |
| io_task_runner_.get(), |
| file_task_runner_.get()); |
| file_system.SetUp(); |
| |
| sync_context_ = new LocalFileSyncContext( |
| dir_.path(), ui_task_runner_.get(), io_task_runner_.get()); |
| ASSERT_EQ(SYNC_STATUS_OK, |
| file_system.MaybeInitializeFileSystemContext(sync_context_.get())); |
| ASSERT_EQ(base::PLATFORM_FILE_OK, file_system.OpenFileSystem()); |
| |
| const char kTestFileData[] = "Lorem ipsum!"; |
| const FileSystemURL kDir(file_system.URL("dir")); |
| const FileSystemURL kFile(file_system.URL("dir/file")); |
| |
| // Either kDir or kFile not exist yet. |
| EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, file_system.FileExists(kDir)); |
| EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, file_system.FileExists(kFile)); |
| |
| // Prepare a temporary file which represents remote file data. |
| const base::FilePath kFilePath(temp_dir.path().Append(FPL("file"))); |
| ASSERT_EQ(static_cast<int>(arraysize(kTestFileData) - 1), |
| file_util::WriteFile(kFilePath, kTestFileData, |
| arraysize(kTestFileData) - 1)); |
| |
| // Calling ApplyChange's with kFilePath should create |
| // kFile along with kDir. |
| FileChange change(FileChange::FILE_CHANGE_ADD_OR_UPDATE, |
| SYNC_FILE_TYPE_FILE); |
| EXPECT_EQ(SYNC_STATUS_OK, |
| ApplyRemoteChange(file_system.file_system_context(), |
| change, kFilePath, kFile, |
| SYNC_FILE_TYPE_UNKNOWN)); |
| |
| // The changes applied by ApplyRemoteChange should not be recorded in |
| // the change tracker. |
| FileSystemURLSet urls; |
| urls.clear(); |
| file_system.GetChangedURLsInTracker(&urls); |
| EXPECT_TRUE(urls.empty()); |
| |
| // Make sure kDir and kFile are created by ApplyRemoteChange. |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.FileExists(kFile)); |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.DirectoryExists(kDir)); |
| |
| sync_context_->ShutdownOnUIThread(); |
| file_system.TearDown(); |
| } |
| |
| } // namespace sync_file_system |