| // 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/syncable_file_operation_runner.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <string> |
| |
| #include "base/files/file.h" |
| #include "base/files/file_util.h" |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/threading/thread_task_runner_handle.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/local_file_sync_context.h" |
| #include "chrome/browser/sync_file_system/local/local_file_sync_status.h" |
| #include "chrome/browser/sync_file_system/local/sync_file_system_backend.h" |
| #include "chrome/browser/sync_file_system/local/syncable_file_system_operation.h" |
| #include "chrome/browser/sync_file_system/syncable_file_system_util.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "content/public/test/test_utils.h" |
| #include "storage/browser/fileapi/file_system_context.h" |
| #include "storage/browser/fileapi/file_system_operation_runner.h" |
| #include "storage/browser/test/mock_blob_url_request_context.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/leveldatabase/src/helpers/memenv/memenv.h" |
| #include "third_party/leveldatabase/src/include/leveldb/env.h" |
| |
| using storage::FileSystemOperation; |
| using storage::FileSystemURL; |
| using content::MockBlobURLRequestContext; |
| using content::ScopedTextBlob; |
| using base::File; |
| |
| namespace sync_file_system { |
| |
| namespace { |
| const std::string kParent = "foo"; |
| const std::string kFile = "foo/file"; |
| const std::string kDir = "foo/dir"; |
| const std::string kChild = "foo/dir/bar"; |
| const std::string kOther = "bar"; |
| } // namespace |
| |
| class SyncableFileOperationRunnerTest : public testing::Test { |
| protected: |
| typedef FileSystemOperation::StatusCallback StatusCallback; |
| |
| // Use the current thread as IO thread so that we can directly call |
| // operations in the tests. |
| SyncableFileOperationRunnerTest() |
| : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), |
| in_memory_env_(leveldb::NewMemEnv(leveldb::Env::Default())), |
| file_system_(GURL("http://example.com"), |
| in_memory_env_.get(), |
| base::ThreadTaskRunnerHandle::Get().get(), |
| base::ThreadTaskRunnerHandle::Get().get()), |
| callback_count_(0), |
| write_status_(File::FILE_ERROR_FAILED), |
| write_bytes_(0), |
| write_complete_(false), |
| url_request_context_(file_system_.file_system_context()), |
| weak_factory_(this) {} |
| |
| void SetUp() override { |
| ASSERT_TRUE(dir_.CreateUniqueTempDir()); |
| |
| file_system_.SetUp(CannedSyncableFileSystem::QUOTA_ENABLED); |
| sync_context_ = |
| new LocalFileSyncContext(dir_.GetPath(), in_memory_env_.get(), |
| base::ThreadTaskRunnerHandle::Get().get(), |
| base::ThreadTaskRunnerHandle::Get().get()); |
| ASSERT_EQ( |
| SYNC_STATUS_OK, |
| file_system_.MaybeInitializeFileSystemContext(sync_context_.get())); |
| |
| ASSERT_EQ(File::FILE_OK, file_system_.OpenFileSystem()); |
| ASSERT_EQ(File::FILE_OK, |
| file_system_.CreateDirectory(URL(kParent))); |
| } |
| |
| void TearDown() override { |
| if (sync_context_.get()) |
| sync_context_->ShutdownOnUIThread(); |
| sync_context_ = nullptr; |
| |
| file_system_.TearDown(); |
| RevokeSyncableFileSystem(); |
| } |
| |
| FileSystemURL URL(const std::string& path) { |
| return file_system_.URL(path); |
| } |
| |
| LocalFileSyncStatus* sync_status() { |
| return file_system_.backend()->sync_context()->sync_status(); |
| } |
| |
| void ResetCallbackStatus() { |
| write_status_ = File::FILE_ERROR_FAILED; |
| write_bytes_ = 0; |
| write_complete_ = false; |
| callback_count_ = 0; |
| } |
| |
| StatusCallback ExpectStatus(const base::Location& location, |
| File::Error expect) { |
| return base::Bind(&SyncableFileOperationRunnerTest::DidFinish, |
| weak_factory_.GetWeakPtr(), location, expect); |
| } |
| |
| FileSystemOperation::WriteCallback GetWriteCallback( |
| const base::Location& location) { |
| return base::Bind(&SyncableFileOperationRunnerTest::DidWrite, |
| weak_factory_.GetWeakPtr(), location); |
| } |
| |
| void DidWrite(const base::Location& location, |
| File::Error status, |
| int64_t bytes, |
| bool complete) { |
| SCOPED_TRACE(testing::Message() << location.ToString()); |
| write_status_ = status; |
| write_bytes_ += bytes; |
| write_complete_ = complete; |
| ++callback_count_; |
| } |
| |
| void DidFinish(const base::Location& location, |
| File::Error expect, |
| File::Error status) { |
| SCOPED_TRACE(testing::Message() << location.ToString()); |
| EXPECT_EQ(expect, status); |
| ++callback_count_; |
| base::RunLoop::QuitCurrentWhenIdleDeprecated(); |
| } |
| |
| bool CreateTempFile(base::FilePath* path) { |
| return base::CreateTemporaryFileInDir(dir_.GetPath(), path); |
| } |
| |
| content::TestBrowserThreadBundle thread_bundle_; |
| |
| base::ScopedTempDir dir_; |
| std::unique_ptr<leveldb::Env> in_memory_env_; |
| |
| CannedSyncableFileSystem file_system_; |
| scoped_refptr<LocalFileSyncContext> sync_context_; |
| |
| int callback_count_; |
| File::Error write_status_; |
| size_t write_bytes_; |
| bool write_complete_; |
| |
| MockBlobURLRequestContext url_request_context_; |
| |
| private: |
| base::WeakPtrFactory<SyncableFileOperationRunnerTest> weak_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SyncableFileOperationRunnerTest); |
| }; |
| |
| TEST_F(SyncableFileOperationRunnerTest, SimpleQueue) { |
| sync_status()->StartSyncing(URL(kFile)); |
| ASSERT_FALSE(sync_status()->IsWritable(URL(kFile))); |
| |
| // The URL is in syncing so the write operations won't run. |
| ResetCallbackStatus(); |
| file_system_.operation_runner()->CreateFile( |
| URL(kFile), false /* exclusive */, |
| ExpectStatus(FROM_HERE, File::FILE_OK)); |
| file_system_.operation_runner()->Truncate( |
| URL(kFile), 1, |
| ExpectStatus(FROM_HERE, File::FILE_OK)); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(0, callback_count_); |
| |
| // Read operations are not blocked (and are executed before queued ones). |
| file_system_.operation_runner()->FileExists( |
| URL(kFile), ExpectStatus(FROM_HERE, File::FILE_ERROR_NOT_FOUND)); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(1, callback_count_); |
| |
| // End syncing (to enable write). |
| sync_status()->EndSyncing(URL(kFile)); |
| ASSERT_TRUE(sync_status()->IsWritable(URL(kFile))); |
| |
| ResetCallbackStatus(); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(2, callback_count_); |
| |
| // Now the file must have been created and updated. |
| ResetCallbackStatus(); |
| file_system_.operation_runner()->FileExists( |
| URL(kFile), ExpectStatus(FROM_HERE, File::FILE_OK)); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(1, callback_count_); |
| } |
| |
| TEST_F(SyncableFileOperationRunnerTest, WriteToParentAndChild) { |
| // First create the kDir directory and kChild in the dir. |
| EXPECT_EQ(File::FILE_OK, file_system_.CreateDirectory(URL(kDir))); |
| EXPECT_EQ(File::FILE_OK, file_system_.CreateFile(URL(kChild))); |
| |
| // Start syncing the kDir directory. |
| sync_status()->StartSyncing(URL(kDir)); |
| ASSERT_FALSE(sync_status()->IsWritable(URL(kDir))); |
| |
| // Writes to kParent and kChild should be all queued up. |
| ResetCallbackStatus(); |
| file_system_.operation_runner()->Truncate( |
| URL(kChild), 1, ExpectStatus(FROM_HERE, File::FILE_OK)); |
| file_system_.operation_runner()->Remove( |
| URL(kParent), true /* recursive */, |
| ExpectStatus(FROM_HERE, File::FILE_OK)); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(0, callback_count_); |
| |
| // Read operations are not blocked (and are executed before queued ones). |
| file_system_.operation_runner()->DirectoryExists( |
| URL(kDir), ExpectStatus(FROM_HERE, File::FILE_OK)); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(1, callback_count_); |
| |
| // Writes to unrelated files must succeed as well. |
| ResetCallbackStatus(); |
| file_system_.operation_runner()->CreateDirectory( |
| URL(kOther), false /* exclusive */, false /* recursive */, |
| ExpectStatus(FROM_HERE, File::FILE_OK)); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(1, callback_count_); |
| |
| // End syncing (to enable write). |
| sync_status()->EndSyncing(URL(kDir)); |
| ASSERT_TRUE(sync_status()->IsWritable(URL(kDir))); |
| |
| ResetCallbackStatus(); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(2, callback_count_); |
| } |
| |
| TEST_F(SyncableFileOperationRunnerTest, CopyAndMove) { |
| // First create the kDir directory and kChild in the dir. |
| EXPECT_EQ(File::FILE_OK, file_system_.CreateDirectory(URL(kDir))); |
| EXPECT_EQ(File::FILE_OK, file_system_.CreateFile(URL(kChild))); |
| |
| // Start syncing the kParent directory. |
| sync_status()->StartSyncing(URL(kParent)); |
| |
| // Copying kDir to other directory should succeed, while moving would fail |
| // (since the source directory is in syncing). |
| ResetCallbackStatus(); |
| file_system_.operation_runner()->Copy( |
| URL(kDir), URL("dest-copy"), storage::FileSystemOperation::OPTION_NONE, |
| storage::FileSystemOperation::ERROR_BEHAVIOR_ABORT, |
| storage::FileSystemOperationRunner::CopyProgressCallback(), |
| ExpectStatus(FROM_HERE, File::FILE_OK)); |
| file_system_.operation_runner()->Move( |
| URL(kDir), |
| URL("dest-move"), |
| storage::FileSystemOperation::OPTION_NONE, |
| ExpectStatus(FROM_HERE, File::FILE_OK)); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(1, callback_count_); |
| |
| // Only "dest-copy1" should exist. |
| EXPECT_EQ(File::FILE_OK, |
| file_system_.DirectoryExists(URL("dest-copy"))); |
| EXPECT_EQ(File::FILE_ERROR_NOT_FOUND, |
| file_system_.DirectoryExists(URL("dest-move"))); |
| |
| // Start syncing the "dest-copy2" directory. |
| sync_status()->StartSyncing(URL("dest-copy2")); |
| |
| // Now the destination is also locked copying kDir should be queued. |
| ResetCallbackStatus(); |
| file_system_.operation_runner()->Copy( |
| URL(kDir), URL("dest-copy2"), storage::FileSystemOperation::OPTION_NONE, |
| storage::FileSystemOperation::ERROR_BEHAVIOR_ABORT, |
| storage::FileSystemOperationRunner::CopyProgressCallback(), |
| ExpectStatus(FROM_HERE, File::FILE_OK)); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(0, callback_count_); |
| |
| // Finish syncing the "dest-copy2" directory to unlock Copy. |
| sync_status()->EndSyncing(URL("dest-copy2")); |
| ResetCallbackStatus(); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(1, callback_count_); |
| |
| // Now we should have "dest-copy2". |
| EXPECT_EQ(File::FILE_OK, |
| file_system_.DirectoryExists(URL("dest-copy2"))); |
| |
| // Finish syncing the kParent to unlock Move. |
| sync_status()->EndSyncing(URL(kParent)); |
| ResetCallbackStatus(); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(1, callback_count_); |
| |
| // Now we should have "dest-move". |
| EXPECT_EQ(File::FILE_OK, |
| file_system_.DirectoryExists(URL("dest-move"))); |
| } |
| |
| TEST_F(SyncableFileOperationRunnerTest, Write) { |
| EXPECT_EQ(File::FILE_OK, file_system_.CreateFile(URL(kFile))); |
| const std::string kData("Lorem ipsum."); |
| ScopedTextBlob blob(url_request_context_, "blob:foo", kData); |
| |
| sync_status()->StartSyncing(URL(kFile)); |
| |
| ResetCallbackStatus(); |
| file_system_.operation_runner()->Write( |
| &url_request_context_, |
| URL(kFile), blob.GetBlobDataHandle(), 0, GetWriteCallback(FROM_HERE)); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(0, callback_count_); |
| |
| sync_status()->EndSyncing(URL(kFile)); |
| ResetCallbackStatus(); |
| |
| while (!write_complete_) |
| content::RunAllTasksUntilIdle(); |
| |
| EXPECT_EQ(File::FILE_OK, write_status_); |
| EXPECT_EQ(kData.size(), write_bytes_); |
| EXPECT_TRUE(write_complete_); |
| } |
| |
| TEST_F(SyncableFileOperationRunnerTest, QueueAndCancel) { |
| sync_status()->StartSyncing(URL(kFile)); |
| ASSERT_FALSE(sync_status()->IsWritable(URL(kFile))); |
| |
| ResetCallbackStatus(); |
| file_system_.operation_runner()->CreateFile( |
| URL(kFile), false /* exclusive */, |
| ExpectStatus(FROM_HERE, File::FILE_ERROR_ABORT)); |
| file_system_.operation_runner()->Truncate( |
| URL(kFile), 1, |
| ExpectStatus(FROM_HERE, File::FILE_ERROR_ABORT)); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(0, callback_count_); |
| |
| ResetCallbackStatus(); |
| |
| // This shouldn't crash nor leak memory. |
| sync_context_->ShutdownOnUIThread(); |
| sync_context_ = nullptr; |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(2, callback_count_); |
| } |
| |
| // Test if CopyInForeignFile runs cooperatively with other Sync operations. |
| TEST_F(SyncableFileOperationRunnerTest, CopyInForeignFile) { |
| const std::string kTestData("test data"); |
| |
| base::FilePath temp_path; |
| ASSERT_TRUE(CreateTempFile(&temp_path)); |
| ASSERT_EQ(static_cast<int>(kTestData.size()), |
| base::WriteFile( |
| temp_path, kTestData.data(), kTestData.size())); |
| |
| sync_status()->StartSyncing(URL(kFile)); |
| ASSERT_FALSE(sync_status()->IsWritable(URL(kFile))); |
| |
| // The URL is in syncing so CopyIn (which is a write operation) won't run. |
| ResetCallbackStatus(); |
| file_system_.operation_runner()->CopyInForeignFile( |
| temp_path, URL(kFile), |
| ExpectStatus(FROM_HERE, File::FILE_OK)); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(0, callback_count_); |
| |
| // End syncing (to enable write). |
| sync_status()->EndSyncing(URL(kFile)); |
| ASSERT_TRUE(sync_status()->IsWritable(URL(kFile))); |
| |
| ResetCallbackStatus(); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(1, callback_count_); |
| |
| // Now the file must have been created and have the same content as temp_path. |
| ResetCallbackStatus(); |
| file_system_.DoVerifyFile( |
| URL(kFile), kTestData, |
| ExpectStatus(FROM_HERE, File::FILE_OK)); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(1, callback_count_); |
| } |
| |
| TEST_F(SyncableFileOperationRunnerTest, Cancel) { |
| // Prepare a file. |
| file_system_.operation_runner()->CreateFile( |
| URL(kFile), false /* exclusive */, |
| ExpectStatus(FROM_HERE, File::FILE_OK)); |
| content::RunAllTasksUntilIdle(); |
| EXPECT_EQ(1, callback_count_); |
| |
| // Run Truncate and immediately cancel. This shouldn't crash. |
| ResetCallbackStatus(); |
| storage::FileSystemOperationRunner::OperationID id = |
| file_system_.operation_runner()->Truncate( |
| URL(kFile), 10, ExpectStatus(FROM_HERE, File::FILE_OK)); |
| file_system_.operation_runner()->Cancel( |
| id, ExpectStatus(FROM_HERE, File::FILE_ERROR_INVALID_OPERATION)); |
| base::RunLoop().Run(); |
| EXPECT_EQ(2, callback_count_); |
| } |
| |
| } // namespace sync_file_system |