diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-07-12 14:07:37 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-07-17 10:29:26 +0000 |
commit | ec02ee4181c49b61fce1c8fb99292dbb8139cc90 (patch) | |
tree | 25cde714b2b71eb639d1cd53f5a22e9ba76e14ef /chromium/storage | |
parent | bb09965444b5bb20b096a291445170876225268d (diff) | |
download | qtwebengine-chromium-ec02ee4181c49b61fce1c8fb99292dbb8139cc90.tar.gz |
BASELINE: Update Chromium to 59.0.3071.134
Change-Id: Id02ef6fb2204c5fd21668a1c3e6911c83b17585a
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/storage')
35 files changed, 8566 insertions, 50 deletions
diff --git a/chromium/storage/browser/BUILD.gn b/chromium/storage/browser/BUILD.gn index d701ae0b049..4fdb007f1de 100644 --- a/chromium/storage/browser/BUILD.gn +++ b/chromium/storage/browser/BUILD.gn @@ -156,6 +156,7 @@ component("browser") { "fileapi/timed_task_helper.h", "fileapi/transient_file_util.cc", "fileapi/transient_file_util.h", + "fileapi/watcher_manager.h", "quota/client_usage_tracker.cc", "quota/client_usage_tracker.h", "quota/quota_callbacks.h", @@ -223,18 +224,66 @@ executable("dump_file_system") { test("storage_unittests") { sources = [ - # Do NOT add storage/ tests here until this target is added to the build - # bots. http://crbug.com/653751 - - # If the sources list is empty, the win_clang builder fails. - # This file will be removed when the real tests are moved over from - # content_unittests to this target. - "crbug653751_unittest.cc", + # This target is in the process of being populated. See + # http://crbug.com/653751 + "blob/blob_data_builder_unittest.cc", + "blob/blob_flattener_unittest.cc", + "blob/blob_memory_controller_unittest.cc", + "blob/blob_reader_unittest.cc", + "blob/blob_slice_unittest.cc", + "blob/blob_storage_context_unittest.cc", + "blob/blob_storage_registry_unittest.cc", + "blob/blob_transport_request_builder_unittest.cc", + "blob/blob_url_request_job_unittest.cc", + "database/database_quota_client_unittest.cc", + "database/database_util_unittest.cc", + "database/databases_table_unittest.cc", + "fileapi/external_mount_points_unittest.cc", + "fileapi/file_system_url_unittest.cc", + "fileapi/file_system_usage_cache_unittest.cc", + "fileapi/isolated_context_unittest.cc", + "fileapi/local_file_stream_reader_unittest.cc", + "fileapi/local_file_stream_writer_unittest.cc", + "fileapi/native_file_util_unittest.cc", + "fileapi/quota/quota_backend_impl_unittest.cc", + "fileapi/quota/quota_reservation_manager_unittest.cc", + "fileapi/sandbox_isolated_origin_database_unittest.cc", + "fileapi/sandbox_prioritized_origin_database_unittest.cc", + "fileapi/timed_task_helper_unittest.cc", ] deps = [ + ":browser", + ":test_support", "//base/test:run_all_unittests", "//base/test:test_support", + "//net:test_support", + "//sql:test_support", + "//testing/gtest", + "//third_party/leveldatabase", + ] +} + +static_library("test_support") { + testonly = true + + sources = [ + "test/async_file_test_helper.cc", + "test/async_file_test_helper.h", + "test/mock_special_storage_policy.cc", + "test/mock_special_storage_policy.h", + "test/test_file_system_backend.cc", + "test/test_file_system_backend.h", + "test/test_file_system_context.cc", + "test/test_file_system_context.h", + "test/test_file_system_options.cc", + "test/test_file_system_options.h", + ] + + deps = [ + ":browser", + "//base/test:test_support", + "//net:test_support", "//testing/gtest", ] } diff --git a/chromium/storage/browser/blob/blob_data_builder_unittest.cc b/chromium/storage/browser/blob/blob_data_builder_unittest.cc new file mode 100644 index 00000000000..f1fc6a9a6a6 --- /dev/null +++ b/chromium/storage/browser/blob/blob_data_builder_unittest.cc @@ -0,0 +1,31 @@ +// Copyright 2016 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 "storage/browser/blob/blob_data_builder.h" + +#include <string> + +#include "base/logging.h" +#include "storage/common/data_element.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace storage { + +TEST(BlobDataBuilderTest, TestFutureFiles) { + const std::string kId = "id"; + + DataElement element; + element.SetToFilePath(BlobDataBuilder::GetFutureFileItemPath(0)); + EXPECT_TRUE(BlobDataBuilder::IsFutureFileItem(element)); + EXPECT_EQ(0ull, BlobDataBuilder::GetFutureFileID(element)); + + BlobDataBuilder builder(kId); + builder.AppendFutureFile(0, 10, 0); + EXPECT_TRUE( + BlobDataBuilder::IsFutureFileItem(builder.items_[0]->data_element())); + EXPECT_EQ(0ull, BlobDataBuilder::GetFutureFileID( + builder.items_[0]->data_element())); +} + +} // namespace storage diff --git a/chromium/storage/browser/blob/blob_flattener_unittest.cc b/chromium/storage/browser/blob/blob_flattener_unittest.cc new file mode 100644 index 00000000000..969fa8b6e1e --- /dev/null +++ b/chromium/storage/browser/blob/blob_flattener_unittest.cc @@ -0,0 +1,283 @@ +// Copyright 2016 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 "storage/browser/blob/blob_storage_context.h" + +#include <memory> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/test/test_simple_task_runner.h" +#include "base/time/time.h" +#include "storage/browser/blob/blob_data_builder.h" +#include "storage/browser/blob/blob_data_handle.h" +#include "storage/browser/blob/blob_data_item.h" +#include "storage/browser/blob/blob_entry.h" +#include "storage/browser/blob/blob_memory_controller.h" +#include "storage/browser/blob/blob_storage_registry.h" +#include "storage/browser/blob/shareable_blob_data_item.h" +#include "storage/common/data_element.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace storage { +namespace { +using base::TestSimpleTaskRunner; +using FileCreationInfo = BlobMemoryController::FileCreationInfo; + +const char kType[] = "type"; +const char kDisposition[] = ""; +const size_t kTestBlobStorageIPCThresholdBytes = 20; +const size_t kTestBlobStorageMaxSharedMemoryBytes = 50; + +const size_t kTestBlobStorageMaxBlobMemorySize = 400; +const uint64_t kTestBlobStorageMaxDiskSpace = 4000; +const uint64_t kTestBlobStorageMinFileSizeBytes = 10; +const uint64_t kTestBlobStorageMaxFileSizeBytes = 100; + +void SaveBlobStatusAndFiles(BlobStatus* status_ptr, + std::vector<FileCreationInfo>* files_ptr, + BlobStatus status, + std::vector<FileCreationInfo> files) { + EXPECT_FALSE(BlobStatusIsError(status)); + *status_ptr = status; + std::move(files.begin(), files.end(), std::back_inserter(*files_ptr)); +} + +} // namespace + +class BlobFlattenerTest : public testing::Test { + protected: + using BlobFlattener = BlobStorageContext::BlobFlattener; + + BlobFlattenerTest() + : fake_file_path_(base::FilePath(FILE_PATH_LITERAL("kFakePath"))) {} + ~BlobFlattenerTest() override {} + + void SetUp() override { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + context_ = base::MakeUnique<BlobStorageContext>(); + } + + void TearDown() override { + base::RunLoop().RunUntilIdle(); + file_runner_->RunPendingTasks(); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(temp_dir_.Delete()); + } + + scoped_refptr<BlobDataItem> CreateDataDescriptionItem(size_t size) { + std::unique_ptr<DataElement> element(new DataElement()); + element->SetToBytesDescription(size); + return scoped_refptr<BlobDataItem>(new BlobDataItem(std::move(element))); + }; + + scoped_refptr<BlobDataItem> CreateDataItem(const char* memory, size_t size) { + std::unique_ptr<DataElement> element(new DataElement()); + element->SetToBytes(memory, size); + return scoped_refptr<BlobDataItem>(new BlobDataItem(std::move(element))); + }; + + scoped_refptr<BlobDataItem> CreateFileItem(size_t offset, size_t size) { + std::unique_ptr<DataElement> element(new DataElement()); + element->SetToFilePathRange(fake_file_path_, offset, size, + base::Time::Max()); + return scoped_refptr<BlobDataItem>(new BlobDataItem(std::move(element))); + }; + + scoped_refptr<BlobDataItem> CreateFutureFileItem(size_t offset, size_t size) { + std::unique_ptr<DataElement> element(new DataElement()); + element->SetToFilePathRange(BlobDataBuilder::GetFutureFileItemPath(0), + offset, size, base::Time()); + return scoped_refptr<BlobDataItem>(new BlobDataItem(std::move(element))); + }; + + std::unique_ptr<BlobDataHandle> SetupBasicBlob(const std::string& id) { + BlobDataBuilder builder(id); + builder.AppendData("1", 1); + builder.set_content_type("text/plain"); + return context_->AddFinishedBlob(builder); + } + + BlobStorageRegistry* registry() { return context_->mutable_registry(); } + + const ShareableBlobDataItem& GetItemInBlob(const std::string& uuid, + size_t index) { + BlobEntry* entry = registry()->GetEntry(uuid); + EXPECT_TRUE(entry); + return *entry->items()[index]; + } + + void SetTestMemoryLimits() { + BlobStorageLimits limits; + limits.max_ipc_memory_size = kTestBlobStorageIPCThresholdBytes; + limits.max_shared_memory_size = kTestBlobStorageMaxSharedMemoryBytes; + limits.max_blob_in_memory_space = kTestBlobStorageMaxBlobMemorySize; + limits.desired_max_disk_space = kTestBlobStorageMaxDiskSpace; + limits.effective_max_disk_space = kTestBlobStorageMaxDiskSpace; + limits.min_page_file_size = kTestBlobStorageMinFileSizeBytes; + limits.max_file_size = kTestBlobStorageMaxFileSizeBytes; + context_->mutable_memory_controller()->set_limits_for_testing(limits); + } + + base::FilePath fake_file_path_; + base::ScopedTempDir temp_dir_; + scoped_refptr<TestSimpleTaskRunner> file_runner_ = new TestSimpleTaskRunner(); + + base::MessageLoop fake_io_message_loop; + std::unique_ptr<BlobStorageContext> context_; +}; + +TEST_F(BlobFlattenerTest, NoBlobItems) { + const std::string kBlobUUID = "kId"; + + BlobDataBuilder builder(kBlobUUID); + builder.AppendData("hi", 2u); + builder.AppendFile(fake_file_path_, 0u, 10u, base::Time::Max()); + BlobEntry output(kType, kDisposition); + BlobFlattener flattener(builder, &output, registry()); + + EXPECT_EQ(BlobStatus::PENDING_QUOTA, flattener.status); + EXPECT_EQ(0u, flattener.dependent_blobs.size()); + EXPECT_EQ(0u, flattener.copies.size()); + EXPECT_EQ(12u, flattener.total_size); + EXPECT_EQ(2u, flattener.transport_quota_needed); + + ASSERT_EQ(2u, output.items().size()); + EXPECT_EQ(*CreateDataItem("hi", 2u), *output.items()[0]->item()); + EXPECT_EQ(*CreateFileItem(0, 10u), *output.items()[1]->item()); +} + +TEST_F(BlobFlattenerTest, ErrorCases) { + const std::string kBlobUUID = "kId"; + const std::string kBlob2UUID = "kId2"; + + // Invalid blob reference. + { + BlobDataBuilder builder(kBlobUUID); + builder.AppendBlob("doesnotexist"); + BlobEntry output(kType, kDisposition); + BlobFlattener flattener(builder, &output, registry()); + EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS, flattener.status); + } + + // Circular reference. + { + BlobDataBuilder builder(kBlobUUID); + builder.AppendBlob(kBlobUUID); + BlobEntry output(kType, kDisposition); + BlobFlattener flattener(builder, &output, registry()); + EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS, flattener.status); + } + + // Bad slice. + { + std::unique_ptr<BlobDataHandle> handle = SetupBasicBlob(kBlob2UUID); + BlobDataBuilder builder(kBlobUUID); + builder.AppendBlob(kBlob2UUID, 1, 2); + BlobEntry output(kType, kDisposition); + BlobFlattener flattener(builder, &output, registry()); + EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS, flattener.status); + } +} + +TEST_F(BlobFlattenerTest, BlobWithSlices) { + const std::string kBlobUUID = "kId"; + const std::string kDataBlob = "kId2"; + const std::string kFileBlob = "kId3"; + const std::string kPendingFileBlob = "kId4"; + + // We have the following: + // * data, + // * sliced data blob, + // * file + // * full data blob, + // * pending data, + + context_ = + base::MakeUnique<BlobStorageContext>(temp_dir_.GetPath(), file_runner_); + SetTestMemoryLimits(); + + std::unique_ptr<BlobDataHandle> data_blob; + { + BlobDataBuilder builder(kDataBlob); + builder.AppendData("12345", 5); + builder.set_content_type("text/plain"); + data_blob = context_->AddFinishedBlob(builder); + } + + std::unique_ptr<BlobDataHandle> file_blob; + { + BlobDataBuilder builder(kFileBlob); + builder.AppendFile(fake_file_path_, 1u, 10u, base::Time::Max()); + file_blob = context_->AddFinishedBlob(builder); + } + + BlobStatus file_status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; + std::vector<FileCreationInfo> file_handles; + std::unique_ptr<BlobDataHandle> future_file_blob; + { + BlobDataBuilder builder(kPendingFileBlob); + builder.AppendFutureFile(0u, 2u, 0); + builder.AppendFutureFile(2u, 5u, 0); + future_file_blob = context_->BuildBlob( + builder, + base::Bind(&SaveBlobStatusAndFiles, &file_status, &file_handles)); + } + + BlobDataBuilder builder(kBlobUUID); + builder.AppendData("hi", 2u); + builder.AppendBlob(kDataBlob, 1u, 2u); + builder.AppendFile(fake_file_path_, 3u, 5u, base::Time::Max()); + builder.AppendBlob(kDataBlob); + builder.AppendBlob(kFileBlob, 1u, 3u); + builder.AppendFutureData(12u); + builder.AppendBlob(kPendingFileBlob, 1u, 3u); + + BlobEntry output(kType, kDisposition); + BlobFlattener flattener(builder, &output, registry()); + EXPECT_EQ(BlobStatus::PENDING_QUOTA, flattener.status); + + EXPECT_EQ(3u, flattener.dependent_blobs.size()); + EXPECT_EQ(32u, flattener.total_size); + EXPECT_EQ(14u, flattener.transport_quota_needed); + EXPECT_EQ(2u, flattener.copy_quota_needed); + + ASSERT_EQ(8u, output.items().size()); + EXPECT_EQ(*CreateDataItem("hi", 2u), *output.items()[0]->item()); + EXPECT_EQ(*CreateDataDescriptionItem(2u), *output.items()[1]->item()); + EXPECT_EQ(*CreateFileItem(3u, 5u), *output.items()[2]->item()); + EXPECT_EQ(GetItemInBlob(kDataBlob, 0), *output.items()[3]); + EXPECT_EQ(*CreateFileItem(2u, 3u), *output.items()[4]->item()); + EXPECT_EQ(*CreateDataDescriptionItem(12u), *output.items()[5]->item()); + EXPECT_EQ(*CreateFutureFileItem(1u, 1u), *output.items()[6]->item()); + EXPECT_EQ(*CreateFutureFileItem(2u, 2u), *output.items()[7]->item()); + + // We're copying items at index 1, 6, and 7. + ASSERT_EQ(3u, flattener.copies.size()); + EXPECT_EQ(*flattener.copies[0].dest_item, *output.items()[1]); + EXPECT_EQ(GetItemInBlob(kDataBlob, 0), *flattener.copies[0].source_item); + EXPECT_EQ(1u, flattener.copies[0].source_item_offset); + EXPECT_EQ(*flattener.copies[1].dest_item, *output.items()[6]); + EXPECT_EQ(GetItemInBlob(kPendingFileBlob, 0), + *flattener.copies[1].source_item); + EXPECT_EQ(1u, flattener.copies[1].source_item_offset); + EXPECT_EQ(*flattener.copies[2].dest_item, *output.items()[7]); + EXPECT_EQ(GetItemInBlob(kPendingFileBlob, 1), + *flattener.copies[2].source_item); + EXPECT_EQ(0u, flattener.copies[2].source_item_offset); + + // Clean up temp files. + EXPECT_TRUE(file_runner_->HasPendingTask()); + file_runner_->RunPendingTasks(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, file_status); + EXPECT_FALSE(file_handles.empty()); +} + +} // namespace storage diff --git a/chromium/storage/browser/blob/blob_memory_controller_unittest.cc b/chromium/storage/browser/blob/blob_memory_controller_unittest.cc new file mode 100644 index 00000000000..918315b378e --- /dev/null +++ b/chromium/storage/browser/blob/blob_memory_controller_unittest.cc @@ -0,0 +1,1123 @@ +// Copyright 2016 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 "storage/browser/blob/blob_memory_controller.h" + +#include "base/bind.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/sys_info.h" +#include "base/test/test_simple_task_runner.h" +#include "base/threading/thread_restrictions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "storage/browser/blob/blob_data_builder.h" +#include "storage/browser/blob/blob_data_item.h" +#include "storage/browser/blob/shareable_blob_data_item.h" +#include "storage/common/data_element.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace storage { + +using Strategy = BlobMemoryController::Strategy; +using FileCreationInfo = BlobMemoryController::FileCreationInfo; +using base::TestSimpleTaskRunner; +using ItemState = ShareableBlobDataItem::State; +using QuotaAllocationTask = BlobMemoryController::QuotaAllocationTask; + +const std::string kBlobStorageDirectory = "blob_storage"; +const size_t kTestBlobStorageIPCThresholdBytes = 20; +const size_t kTestBlobStorageMaxSharedMemoryBytes = 50; +const size_t kTestBlobStorageMaxBlobMemorySize = 500; +const uint64_t kTestBlobStorageMaxDiskSpace = 1000; +const uint64_t kTestBlobStorageMinFileSizeBytes = 10; +const uint64_t kTestBlobStorageMaxFileSizeBytes = 100; + +const uint64_t kTestSmallBlobStorageMaxDiskSpace = 100; + +static int64_t sFakeDiskSpace = 0; +static bool sFakeDiskSpaceCalled = true; + +int64_t FakeDiskSpaceMethod(const base::FilePath& path) { + EXPECT_FALSE(sFakeDiskSpaceCalled); + sFakeDiskSpaceCalled = true; + return sFakeDiskSpace; +} + +class BlobMemoryControllerTest : public testing::Test { + protected: + BlobMemoryControllerTest() {} + + void SetUp() override { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + base::ThreadRestrictions::SetIOAllowed(false); + }; + + void TearDown() override { + files_created_.clear(); + // Make sure we clean up the files. + base::RunLoop().RunUntilIdle(); + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + base::ThreadRestrictions::SetIOAllowed(true); + ASSERT_TRUE(temp_dir_.Delete()); + } + + void AssertEnoughDiskSpace() { + base::ThreadRestrictions::SetIOAllowed(true); + ASSERT_GT(base::SysInfo::AmountOfFreeDiskSpace(temp_dir_.GetPath()), + static_cast<int64_t>(kTestBlobStorageMaxDiskSpace)) + << "Bot doesn't have enough disk space to run these tests."; + base::ThreadRestrictions::SetIOAllowed(false); + } + + std::vector<scoped_refptr<ShareableBlobDataItem>> CreateSharedDataItems( + const BlobDataBuilder& builder) { + std::vector<scoped_refptr<ShareableBlobDataItem>> result; + for (size_t i = 0; i < builder.items_.size(); ++i) { + result.push_back(make_scoped_refptr(new ShareableBlobDataItem( + builder.items_[i], ShareableBlobDataItem::QUOTA_NEEDED))); + } + return result; + } + + void SetTestMemoryLimits(BlobMemoryController* controller) { + BlobStorageLimits limits; + limits.max_ipc_memory_size = kTestBlobStorageIPCThresholdBytes; + limits.max_shared_memory_size = kTestBlobStorageMaxSharedMemoryBytes; + limits.max_blob_in_memory_space = kTestBlobStorageMaxBlobMemorySize; + limits.desired_max_disk_space = kTestBlobStorageMaxDiskSpace; + limits.effective_max_disk_space = kTestBlobStorageMaxDiskSpace; + limits.min_page_file_size = kTestBlobStorageMinFileSizeBytes; + limits.max_file_size = kTestBlobStorageMaxFileSizeBytes; + controller->set_limits_for_testing(limits); + } + + void SetSmallDiskTestMemoryLimits(BlobMemoryController* controller) { + BlobStorageLimits limits; + limits.max_ipc_memory_size = kTestBlobStorageIPCThresholdBytes; + limits.max_shared_memory_size = kTestBlobStorageMaxSharedMemoryBytes; + limits.max_blob_in_memory_space = kTestBlobStorageMaxBlobMemorySize; + limits.desired_max_disk_space = kTestSmallBlobStorageMaxDiskSpace; + limits.effective_max_disk_space = kTestSmallBlobStorageMaxDiskSpace; + limits.min_page_file_size = kTestBlobStorageMinFileSizeBytes; + limits.max_file_size = kTestBlobStorageMaxFileSizeBytes; + controller->set_limits_for_testing(limits); + } + + void SaveFileCreationInfo(std::vector<FileCreationInfo> info, bool success) { + file_quota_result_ = success; + if (success) { + files_created_.swap(info); + } + } + + void SaveMemoryRequestToOutput(bool* output, bool success) { + ASSERT_TRUE(output); + *output = success; + } + void SaveMemoryRequest(bool success) { memory_quota_result_ = success; } + + BlobMemoryController::FileQuotaRequestCallback GetFileCreationCallback() { + return base::Bind(&BlobMemoryControllerTest::SaveFileCreationInfo, + base::Unretained(this)); + } + + BlobMemoryController::MemoryQuotaRequestCallback GetMemoryRequestCallback() { + return base::Bind(&BlobMemoryControllerTest::SaveMemoryRequest, + base::Unretained(this)); + } + + BlobMemoryController::MemoryQuotaRequestCallback + GetMemoryRequestCallbackToOutput(bool* output) { + return base::Bind(&BlobMemoryControllerTest::SaveMemoryRequestToOutput, + base::Unretained(this), output); + } + + void RunFileThreadTasks() { + base::ThreadRestrictions::SetIOAllowed(true); + file_runner_->RunPendingTasks(); + base::ThreadRestrictions::SetIOAllowed(false); + } + + bool HasMemoryAllocation(ShareableBlobDataItem* item) { + return static_cast<bool>(item->memory_allocation_); + } + + void set_disk_space(int64_t space) { + sFakeDiskSpaceCalled = false; + sFakeDiskSpace = space; + } + + void ExpectDiskSpaceCalled() { EXPECT_TRUE(sFakeDiskSpaceCalled); } + + bool file_quota_result_ = false; + base::ScopedTempDir temp_dir_; + std::vector<FileCreationInfo> files_created_; + bool memory_quota_result_ = false; + + scoped_refptr<TestSimpleTaskRunner> file_runner_ = new TestSimpleTaskRunner(); + + base::MessageLoop fake_io_message_loop_; +}; + +TEST_F(BlobMemoryControllerTest, Strategy) { + { + BlobMemoryController controller(temp_dir_.GetPath(), nullptr); + SetTestMemoryLimits(&controller); + + // No transportation needed. + EXPECT_EQ(Strategy::NONE_NEEDED, controller.DetermineStrategy(0, 0)); + + // IPC. + EXPECT_EQ(Strategy::IPC, controller.DetermineStrategy( + 0, kTestBlobStorageIPCThresholdBytes)); + + // Shared Memory. + EXPECT_EQ( + Strategy::SHARED_MEMORY, + controller.DetermineStrategy(kTestBlobStorageIPCThresholdBytes, + kTestBlobStorageMaxSharedMemoryBytes)); + EXPECT_EQ( + Strategy::SHARED_MEMORY, + controller.DetermineStrategy(0, kTestBlobStorageMaxBlobMemorySize)); + + // Too large. + EXPECT_EQ( + Strategy::TOO_LARGE, + controller.DetermineStrategy(0, kTestBlobStorageMaxBlobMemorySize + 1)); + } + { + // Enable disk, and check file strategies. + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + SetTestMemoryLimits(&controller); + + EXPECT_EQ( + Strategy::SHARED_MEMORY, + controller.DetermineStrategy(0, kTestBlobStorageMaxBlobMemorySize - + kTestBlobStorageMinFileSizeBytes)); + EXPECT_EQ(Strategy::FILE, controller.DetermineStrategy( + 0, kTestBlobStorageMaxBlobMemorySize - + kTestBlobStorageMinFileSizeBytes + 1)); + + EXPECT_EQ(Strategy::FILE, controller.DetermineStrategy( + 0, kTestBlobStorageMaxBlobMemorySize)); + + // Too large for disk. + EXPECT_EQ(Strategy::TOO_LARGE, controller.DetermineStrategy( + 0, kTestBlobStorageMaxDiskSpace + 1)); + } + { + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + SetSmallDiskTestMemoryLimits(&controller); + + EXPECT_TRUE(controller.CanReserveQuota(kTestBlobStorageMaxBlobMemorySize)); + // Since our disk is too small, this should be sent with shared memory. + EXPECT_EQ( + Strategy::SHARED_MEMORY, + controller.DetermineStrategy(0, kTestBlobStorageMaxBlobMemorySize)); + } +} + +TEST_F(BlobMemoryControllerTest, GrantMemory) { + const std::string kId = "id"; + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + SetTestMemoryLimits(&controller); + + BlobDataBuilder builder(kId); + builder.AppendFutureData(10); + builder.AppendFutureData(20); + builder.AppendFutureData(30); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(builder); + + controller.ReserveMemoryQuota(items, GetMemoryRequestCallback()); + EXPECT_TRUE(memory_quota_result_); + EXPECT_EQ(ItemState::QUOTA_GRANTED, items[0]->state()); + EXPECT_TRUE(HasMemoryAllocation(items[0].get())); + EXPECT_EQ(ItemState::QUOTA_GRANTED, items[1]->state()); + EXPECT_TRUE(HasMemoryAllocation(items[0].get())); + EXPECT_EQ(ItemState::QUOTA_GRANTED, items[2]->state()); + EXPECT_TRUE(HasMemoryAllocation(items[0].get())); +} + +TEST_F(BlobMemoryControllerTest, SimpleMemoryRequest) { + const std::string kId = "id"; + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + SetTestMemoryLimits(&controller); + + // Add memory item that is the memory quota. + BlobDataBuilder builder(kId); + builder.AppendFutureData(kTestBlobStorageMaxBlobMemorySize); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(builder); + + base::WeakPtr<QuotaAllocationTask> task = + controller.ReserveMemoryQuota(items, GetMemoryRequestCallback()); + EXPECT_EQ(nullptr, task); + EXPECT_TRUE(memory_quota_result_); + memory_quota_result_ = false; + EXPECT_EQ(ItemState::QUOTA_GRANTED, items[0]->state()); + EXPECT_FALSE(file_runner_->HasPendingTask()); + EXPECT_EQ(kTestBlobStorageMaxBlobMemorySize, controller.memory_usage()); + EXPECT_EQ(0u, controller.disk_usage()); + + items.clear(); + EXPECT_EQ(0u, controller.memory_usage()); +} + +TEST_F(BlobMemoryControllerTest, PageToDisk) { + const std::string kId = "id"; + const std::string kId2 = "id2"; + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + SetTestMemoryLimits(&controller); + AssertEnoughDiskSpace(); + + char kData[kTestBlobStorageMaxBlobMemorySize]; + std::memset(kData, 'e', kTestBlobStorageMaxBlobMemorySize); + + // Add memory item that is the memory quota. + BlobDataBuilder builder(kId); + builder.AppendFutureData(kTestBlobStorageMaxBlobMemorySize); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(builder); + + controller.ReserveMemoryQuota(items, GetMemoryRequestCallback()); + EXPECT_TRUE(memory_quota_result_); + memory_quota_result_ = false; + EXPECT_EQ(ItemState::QUOTA_GRANTED, items[0]->state()); + EXPECT_FALSE(file_runner_->HasPendingTask()); + EXPECT_EQ(kTestBlobStorageMaxBlobMemorySize, controller.memory_usage()); + EXPECT_EQ(0u, controller.disk_usage()); + + // Create an item that is just a little too big. + BlobDataBuilder builder2(kId2); + builder2.AppendFutureData(kTestBlobStorageMinFileSizeBytes + 1); + + // Reserve memory, which should request successfuly but we can't fit it yet + // (no callback). + std::vector<scoped_refptr<ShareableBlobDataItem>> items2 = + CreateSharedDataItems(builder2); + base::WeakPtr<QuotaAllocationTask> task = + controller.ReserveMemoryQuota(items2, GetMemoryRequestCallback()); + EXPECT_NE(nullptr, task); + // We don't count the usage yet. + EXPECT_EQ(kTestBlobStorageMaxBlobMemorySize, controller.memory_usage()); + EXPECT_EQ(0u, controller.disk_usage()); + + EXPECT_FALSE(memory_quota_result_); + EXPECT_EQ(ItemState::QUOTA_REQUESTED, items2[0]->state()); + EXPECT_FALSE(file_runner_->HasPendingTask()); + + // Add our original item as populated so it's paged to disk. + items[0]->item()->data_element_ptr()->SetToBytes( + kData, kTestBlobStorageMaxBlobMemorySize); + items[0]->set_state(ItemState::POPULATED_WITH_QUOTA); + controller.NotifyMemoryItemsUsed(items); + + EXPECT_TRUE(file_runner_->HasPendingTask()); + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + // items2 are successfuly allocated. + EXPECT_EQ(nullptr, task); + EXPECT_EQ(ItemState::QUOTA_GRANTED, items2[0]->state()); + EXPECT_EQ(DataElement::TYPE_FILE, items[0]->item()->type()); + EXPECT_EQ(kTestBlobStorageMinFileSizeBytes + 1, controller.memory_usage()); + EXPECT_EQ(kTestBlobStorageMaxBlobMemorySize, controller.disk_usage()); + + EXPECT_FALSE(controller.CanReserveQuota(kTestBlobStorageMaxDiskSpace)); + + items2.clear(); + EXPECT_EQ(0u, controller.memory_usage()); + items.clear(); + EXPECT_TRUE(file_runner_->HasPendingTask()); + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0u, controller.disk_usage()); +} + +TEST_F(BlobMemoryControllerTest, NoDiskTooLarge) { + BlobMemoryController controller(temp_dir_.GetPath(), nullptr); + SetTestMemoryLimits(&controller); + + EXPECT_FALSE(controller.CanReserveQuota(kTestBlobStorageMaxBlobMemorySize + + kTestBlobStorageMinFileSizeBytes + + 1)); +} + +TEST_F(BlobMemoryControllerTest, TooLargeForDisk) { + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + SetTestMemoryLimits(&controller); + + EXPECT_FALSE(controller.CanReserveQuota(kTestBlobStorageMaxDiskSpace + 1)); +} + +TEST_F(BlobMemoryControllerTest, CancelMemoryRequest) { + const std::string kId = "id"; + const std::string kId2 = "id2"; + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + SetTestMemoryLimits(&controller); + + char kData[kTestBlobStorageMaxBlobMemorySize]; + std::memset(kData, 'e', kTestBlobStorageMaxBlobMemorySize); + + // Add memory item that is the memory quota. + BlobDataBuilder builder(kId); + builder.AppendFutureData(kTestBlobStorageMaxBlobMemorySize); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(builder); + + controller.ReserveMemoryQuota(items, GetMemoryRequestCallback()); + + // Create an item that is just a little too big. + BlobDataBuilder builder2(kId2); + builder2.AppendFutureData(kTestBlobStorageMinFileSizeBytes + 1); + + // Reserve memory, which should request successfuly but we can't fit it yet + // (no callback). + std::vector<scoped_refptr<ShareableBlobDataItem>> items2 = + CreateSharedDataItems(builder2); + base::WeakPtr<QuotaAllocationTask> task = + controller.ReserveMemoryQuota(items2, GetMemoryRequestCallback()); + // We don't count the usage yet. + EXPECT_EQ(kTestBlobStorageMaxBlobMemorySize, controller.memory_usage()); + EXPECT_EQ(0u, controller.disk_usage()); + + // Add our original item as populated so we start paging to disk. + items[0]->item()->data_element_ptr()->SetToBytes( + kData, kTestBlobStorageMaxBlobMemorySize); + items[0]->set_state(ItemState::POPULATED_WITH_QUOTA); + controller.NotifyMemoryItemsUsed(items); + + EXPECT_TRUE(file_runner_->HasPendingTask()); + EXPECT_TRUE(task); + + task->Cancel(); + EXPECT_FALSE(task); + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(ItemState::QUOTA_REQUESTED, items2[0]->state()); + EXPECT_EQ(DataElement::TYPE_FILE, items[0]->item()->type()); + EXPECT_EQ(0u, controller.memory_usage()); + EXPECT_EQ(kTestBlobStorageMaxBlobMemorySize, controller.disk_usage()); + + items.clear(); + // Run cleanup tasks from the ShareableFileReferences. + base::RunLoop().RunUntilIdle(); + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(0u, controller.disk_usage()); +} + +TEST_F(BlobMemoryControllerTest, FileRequest) { + const std::string kId = "id"; + const size_t kBlobSize = kTestBlobStorageMaxBlobMemorySize + 1; + + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + SetTestMemoryLimits(&controller); + + char kData[kBlobSize]; + std::memset(kData, 'e', kBlobSize); + + // Add item that is the file quota. + BlobDataBuilder builder(kId); + builder.AppendFutureFile(0, kBlobSize, 0); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(builder); + + file_quota_result_ = false; + base::WeakPtr<QuotaAllocationTask> task = + controller.ReserveFileQuota(items, GetFileCreationCallback()); + EXPECT_TRUE(task); + EXPECT_FALSE(file_quota_result_); + EXPECT_EQ(ItemState::QUOTA_REQUESTED, items[0]->state()); + EXPECT_TRUE(file_runner_->HasPendingTask()); + EXPECT_EQ(0u, controller.memory_usage()); + EXPECT_EQ(kBlobSize, controller.disk_usage()); + + EXPECT_FALSE(controller.CanReserveQuota(kTestBlobStorageMaxDiskSpace)); + + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(file_quota_result_); + EXPECT_FALSE(file_runner_->HasPendingTask()); + EXPECT_FALSE(task); + + // Do the work to populate the file. + EXPECT_EQ(1u, files_created_.size()); + EXPECT_TRUE( + builder.PopulateFutureFile(0, std::move(files_created_[0].file_reference), + files_created_[0].last_modified)); + base::ThreadRestrictions::SetIOAllowed(true); + files_created_.clear(); + base::ThreadRestrictions::SetIOAllowed(false); + EXPECT_EQ(DataElement::TYPE_FILE, items[0]->item()->type()); + EXPECT_FALSE( + BlobDataBuilder::IsFutureFileItem(items[0]->item()->data_element())); + + builder.Clear(); + items.clear(); + // Run cleanup tasks from the ShareableFileReferences. + base::RunLoop().RunUntilIdle(); + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(0u, controller.disk_usage()); +} + +TEST_F(BlobMemoryControllerTest, CancelFileRequest) { + const std::string kId = "id"; + const size_t kBlobSize = kTestBlobStorageMaxBlobMemorySize + 1; + + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + SetTestMemoryLimits(&controller); + + char kData[kBlobSize]; + std::memset(kData, 'e', kBlobSize); + + // Add memory item that is the memory quota. + BlobDataBuilder builder(kId); + builder.AppendFutureFile(0, kBlobSize, 0); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(builder); + + base::WeakPtr<QuotaAllocationTask> task = + controller.ReserveFileQuota(items, GetFileCreationCallback()); + EXPECT_TRUE(task); + EXPECT_EQ(ItemState::QUOTA_REQUESTED, items[0]->state()); + EXPECT_TRUE(file_runner_->HasPendingTask()); + EXPECT_EQ(0u, controller.memory_usage()); + EXPECT_EQ(kBlobSize, controller.disk_usage()); + + task->Cancel(); + EXPECT_FALSE(task); + EXPECT_EQ(0ull, controller.disk_usage()); + + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); +} + +TEST_F(BlobMemoryControllerTest, MultipleFilesPaged) { + const std::string kId1 = "id"; + const size_t kSize1 = kTestBlobStorageMaxFileSizeBytes; + char kData1[kSize1]; + std::memset(kData1, 'e', kSize1); + + const std::string kId2 = "id2"; + const size_t kSize2 = kTestBlobStorageMaxFileSizeBytes; + char kData2[kSize2]; + std::memset(kData2, 'f', kSize2); + + const std::string kId3 = "id3"; + const size_t kSize3 = kTestBlobStorageMaxBlobMemorySize - 1; + + // Assert we shouldn't trigger paging preemptively. + ASSERT_LE(kSize1 + kSize2, kTestBlobStorageMaxBlobMemorySize); + + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + SetTestMemoryLimits(&controller); + AssertEnoughDiskSpace(); + + // We add two items that should be their own files when we page to disk, and + // then add the last item to trigger the paging. + + BlobDataBuilder builder1(kId1); + builder1.AppendFutureData(kSize1); + BlobDataBuilder builder2(kId2); + builder2.AppendFutureData(kSize2); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items1 = + CreateSharedDataItems(builder1); + std::vector<scoped_refptr<ShareableBlobDataItem>> items2 = + CreateSharedDataItems(builder2); + + memory_quota_result_ = false; + controller.ReserveMemoryQuota(items1, GetMemoryRequestCallback()); + EXPECT_TRUE(memory_quota_result_); + memory_quota_result_ = false; + controller.ReserveMemoryQuota(items2, GetMemoryRequestCallback()); + EXPECT_TRUE(memory_quota_result_); + EXPECT_EQ(ItemState::QUOTA_GRANTED, items1[0]->state()); + EXPECT_EQ(ItemState::QUOTA_GRANTED, items2[0]->state()); + EXPECT_FALSE(file_runner_->HasPendingTask()); + EXPECT_EQ(kSize1 + kSize2, controller.memory_usage()); + EXPECT_EQ(0u, controller.disk_usage()); + + // Create an item that is too big. + BlobDataBuilder builder3(kId3); + builder3.AppendFutureData(kSize3); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items3 = + CreateSharedDataItems(builder3); + memory_quota_result_ = false; + controller.ReserveMemoryQuota(items3, GetMemoryRequestCallback()); + EXPECT_FALSE(memory_quota_result_); + + EXPECT_EQ(ItemState::QUOTA_REQUESTED, items3[0]->state()); + EXPECT_FALSE(file_runner_->HasPendingTask()); + + // Add our original item as populated so it's paged to disk. + items1[0]->item()->data_element_ptr()->SetToBytes(kData1, kSize1); + items1[0]->set_state(ItemState::POPULATED_WITH_QUOTA); + items2[0]->item()->data_element_ptr()->SetToBytes(kData2, kSize2); + items2[0]->set_state(ItemState::POPULATED_WITH_QUOTA); + + std::vector<scoped_refptr<ShareableBlobDataItem>> both_items = {items1[0], + items2[0]}; + controller.NotifyMemoryItemsUsed(both_items); + both_items.clear(); + + EXPECT_TRUE(file_runner_->HasPendingTask()); + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(memory_quota_result_); + EXPECT_EQ(ItemState::QUOTA_GRANTED, items3[0]->state()); + EXPECT_EQ(DataElement::TYPE_FILE, items1[0]->item()->type()); + EXPECT_EQ(DataElement::TYPE_FILE, items2[0]->item()->type()); + EXPECT_NE(items1[0]->item()->path(), items2[0]->item()->path()); + EXPECT_EQ(kSize3, controller.memory_usage()); + EXPECT_EQ(kSize1 + kSize2, controller.disk_usage()); + + items1.clear(); + items2.clear(); + items3.clear(); + + EXPECT_EQ(0u, controller.memory_usage()); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(file_runner_->HasPendingTask()); + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0u, controller.disk_usage()); +} + +TEST_F(BlobMemoryControllerTest, FullEviction) { + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + SetTestMemoryLimits(&controller); + AssertEnoughDiskSpace(); + + char kData[1]; + kData[0] = 'e'; + + // Create a bunch of small stuff. + std::vector<scoped_refptr<ShareableBlobDataItem>> small_items; + for (size_t i = 0; + i < kTestBlobStorageMaxBlobMemorySize - kTestBlobStorageMinFileSizeBytes; + i++) { + BlobDataBuilder builder("fake"); + builder.AppendData(kData, 1); + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(builder); + base::WeakPtr<QuotaAllocationTask> memory_task = + controller.ReserveMemoryQuota(items, GetMemoryRequestCallback()); + EXPECT_FALSE(memory_task); + items[0]->set_state(ItemState::POPULATED_WITH_QUOTA); + small_items.insert(small_items.end(), items.begin(), items.end()); + } + controller.NotifyMemoryItemsUsed(small_items); + EXPECT_FALSE(file_runner_->HasPendingTask()); + EXPECT_EQ( + kTestBlobStorageMaxBlobMemorySize - kTestBlobStorageMinFileSizeBytes, + controller.memory_usage()); + + // Create maximum size blob to evict ALL small stuff. + BlobDataBuilder builder("fake"); + builder.AppendFutureData(kTestBlobStorageMaxBlobMemorySize - + kTestBlobStorageMinFileSizeBytes); + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(builder); + + memory_quota_result_ = false; + base::WeakPtr<QuotaAllocationTask> memory_task = + controller.ReserveMemoryQuota(items, GetMemoryRequestCallback()); + EXPECT_TRUE(memory_task); + EXPECT_TRUE(file_runner_->HasPendingTask()); + + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ( + kTestBlobStorageMaxBlobMemorySize - kTestBlobStorageMinFileSizeBytes, + controller.memory_usage()); + EXPECT_EQ( + kTestBlobStorageMaxBlobMemorySize - kTestBlobStorageMinFileSizeBytes, + controller.disk_usage()); + + EXPECT_TRUE(memory_quota_result_); +} + +TEST_F(BlobMemoryControllerTest, PagingStopsWhenFull) { + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + SetTestMemoryLimits(&controller); + AssertEnoughDiskSpace(); + const size_t kTotalBlobStorageSize = + kTestBlobStorageMaxDiskSpace + kTestBlobStorageMaxBlobMemorySize; + + const size_t kDataSize = 10u; + const size_t kBlobsThatCanFit = kTotalBlobStorageSize / kDataSize; + const size_t kNumFastBlobs = kTestBlobStorageMaxBlobMemorySize / kDataSize; + char kData[10]; + memset(kData, 'e', kDataSize); + + // Create all of our blobs. + std::vector<scoped_refptr<ShareableBlobDataItem>> all_items; + std::vector<base::WeakPtr<QuotaAllocationTask>> memory_tasks; + bool memory_requested[kBlobsThatCanFit] = {}; + for (size_t i = 0; i < kBlobsThatCanFit; i++) { + BlobDataBuilder builder("fake"); + builder.AppendData(kData, kDataSize); + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(builder); + EXPECT_TRUE(controller.CanReserveQuota(kDataSize)); + EXPECT_EQ((i < kNumFastBlobs) ? Strategy::NONE_NEEDED : Strategy::IPC, + controller.DetermineStrategy(kDataSize, kDataSize)) + << i; + base::WeakPtr<QuotaAllocationTask> memory_task = + controller.ReserveMemoryQuota( + items, GetMemoryRequestCallbackToOutput(&memory_requested[i])); + if (memory_task) { + memory_tasks.push_back(std::move(memory_task)); + } + all_items.insert(all_items.end(), items.begin(), items.end()); + } + // We should have stored all of our memory quota, and no disk yet. + EXPECT_EQ(500u, controller.memory_usage()); + EXPECT_EQ(0ull, controller.disk_usage()); + + EXPECT_FALSE(controller.CanReserveQuota(1u)); + EXPECT_EQ(Strategy::TOO_LARGE, controller.DetermineStrategy(1u, 1ull)); + EXPECT_FALSE(file_runner_->HasPendingTask()); + + for (size_t i = 0; i < kBlobsThatCanFit; i++) { + EXPECT_EQ(i < kBlobsThatCanFit / 3, memory_requested[i]) << i; + if (memory_requested[i] && + all_items[i]->state() != ItemState::POPULATED_WITH_QUOTA) { + EXPECT_TRUE(memory_requested[i]); + all_items[i]->set_state(ItemState::POPULATED_WITH_QUOTA); + std::vector<scoped_refptr<ShareableBlobDataItem>> temp_vector; + temp_vector.push_back(all_items[i]); + controller.NotifyMemoryItemsUsed(temp_vector); + } + } + EXPECT_TRUE(file_runner_->HasPendingTask()); + + // This will schedule one task. Paging starts as soon as there is enough + // memory to page, and multiple pagings can't happen at the same time. + EXPECT_EQ(10ull, controller.disk_usage()); + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + // The rest of the tasks should be scheduled. + EXPECT_TRUE(file_runner_->HasPendingTask()); + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + // Everything in memory should be on disk, and next batch of memory items + // should be granted. + EXPECT_EQ(500u, controller.memory_usage()); + EXPECT_EQ(500ull, controller.disk_usage()); + + // Still can't add anything. + EXPECT_FALSE(controller.CanReserveQuota(1u)); + EXPECT_EQ(Strategy::TOO_LARGE, controller.DetermineStrategy(1u, 1ull)); + + // Flag next batch for saving to disk. + for (size_t i = 0; i < kBlobsThatCanFit; i++) { + // Note: this can fail if the bot's disk is almost full. + EXPECT_EQ(i < kBlobsThatCanFit * 2 / 3, memory_requested[i]) << i; + if (memory_requested[i] && + all_items[i]->state() != ItemState::POPULATED_WITH_QUOTA) { + all_items[i]->set_state(ItemState::POPULATED_WITH_QUOTA); + std::vector<scoped_refptr<ShareableBlobDataItem>> temp_vector; + temp_vector.push_back(all_items[i]); + controller.NotifyMemoryItemsUsed(temp_vector); + } + } + EXPECT_TRUE(file_runner_->HasPendingTask()); + + // Same as before. One page task is scheduled, so run them twice. + EXPECT_EQ(510ull, controller.disk_usage()); + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + // We page one time first, as it blocks paging once it starts. + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + + // All quota should be allocated. + EXPECT_EQ(kTestBlobStorageMaxBlobMemorySize, controller.memory_usage()); + EXPECT_EQ(kTestBlobStorageMaxDiskSpace, controller.disk_usage()); + EXPECT_FALSE(controller.CanReserveQuota(1u)); + EXPECT_EQ(Strategy::TOO_LARGE, controller.DetermineStrategy(1u, 1ull)); + + // Flag last batch as populated. + for (size_t i = 0; i < kBlobsThatCanFit; i++) { + // Note: this can fail if the bot's disk is almost full. + EXPECT_TRUE(memory_requested[i]); + if (memory_requested[i] && + all_items[i]->state() != ItemState::POPULATED_WITH_QUOTA) { + all_items[i]->set_state(ItemState::POPULATED_WITH_QUOTA); + std::vector<scoped_refptr<ShareableBlobDataItem>> temp_vector; + temp_vector.push_back(all_items[i]); + controller.NotifyMemoryItemsUsed(temp_vector); + } + } + + // There should be no more paging to disk, as we've reached the end. + EXPECT_FALSE(file_runner_->HasPendingTask()); + + // All quota should be allocated still. + EXPECT_EQ(500u, controller.memory_usage()); + EXPECT_EQ(1000ull, controller.disk_usage()); + + // Still can't add anything. + EXPECT_FALSE(controller.CanReserveQuota(1u)); + EXPECT_EQ(Strategy::TOO_LARGE, controller.DetermineStrategy(1u, 1ull)); +} + +TEST_F(BlobMemoryControllerTest, DisableDiskWithFileAndMemoryPending) { + const std::string kFirstMemoryId = "id"; + const uint64_t kFirstMemorySize = kTestBlobStorageMaxBlobMemorySize; + const std::string kSecondMemoryId = "id2"; + const uint64_t kSecondMemorySize = 1; + const std::string kFileId = "id2"; + const uint64_t kFileBlobSize = kTestBlobStorageMaxBlobMemorySize; + + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + SetTestMemoryLimits(&controller); + + char kDataMemoryData[kFirstMemorySize]; + std::memset(kDataMemoryData, 'e', kFirstMemorySize); + + // Add first memory item to fill up some memory quota. + BlobDataBuilder builder(kFirstMemoryId); + builder.AppendFutureData(kTestBlobStorageMaxBlobMemorySize); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(builder); + + controller.ReserveMemoryQuota(items, GetMemoryRequestCallback()); + + // Create a second memory item that is just a little too big. + BlobDataBuilder builder2(kSecondMemoryId); + builder2.AppendFutureData(kSecondMemorySize); + + // Reserve memory, which should request successfuly but we can't fit it yet. + std::vector<scoped_refptr<ShareableBlobDataItem>> items2 = + CreateSharedDataItems(builder2); + base::WeakPtr<QuotaAllocationTask> memory_task = + controller.ReserveMemoryQuota(items2, GetMemoryRequestCallback()); + // We don't count the usage yet. + EXPECT_EQ(kTestBlobStorageMaxBlobMemorySize, controller.memory_usage()); + EXPECT_EQ(0u, controller.disk_usage()); + + // Add our original item as populated so we start paging it to disk. + items[0]->item()->data_element_ptr()->SetToBytes(kDataMemoryData, + kFirstMemorySize); + items[0]->set_state(ItemState::POPULATED_WITH_QUOTA); + controller.NotifyMemoryItemsUsed(items); + + EXPECT_TRUE(file_runner_->HasPendingTask()); + EXPECT_TRUE(memory_task); + EXPECT_EQ(kFirstMemorySize, controller.disk_usage()); + + // Add our file item now. + BlobDataBuilder file_builder(kFileId); + file_builder.AppendFutureFile(0, kFileBlobSize, 0); + + std::vector<scoped_refptr<ShareableBlobDataItem>> file_items = + CreateSharedDataItems(file_builder); + + base::WeakPtr<QuotaAllocationTask> file_task = + controller.ReserveFileQuota(file_items, GetFileCreationCallback()); + EXPECT_TRUE(file_task); + EXPECT_TRUE(file_runner_->HasPendingTask()); + + // We should have both memory paging tasks and file paging tasks. + EXPECT_EQ(kFirstMemorySize, controller.memory_usage()); + EXPECT_EQ(kFirstMemorySize + kFileBlobSize, controller.disk_usage()); + file_quota_result_ = true; + memory_quota_result_ = true; + + files_created_.clear(); + + // Disable paging! This should cancel all file-related tasks and leave us with + // only the first memory item. + controller.DisableFilePaging(base::File::FILE_ERROR_FAILED); + EXPECT_FALSE(file_quota_result_); + EXPECT_FALSE(memory_quota_result_); + EXPECT_FALSE(file_task); + EXPECT_FALSE(memory_task); + + file_items.clear(); + items.clear(); + + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(0ull, controller.disk_usage()); + EXPECT_EQ(0ull, controller.memory_usage()); +} + +TEST_F(BlobMemoryControllerTest, DiskSpaceTooSmallForItem) { + const std::string kFileId = "id2"; + const uint64_t kFileBlobSize = kTestBlobStorageMaxBlobMemorySize; + + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + controller.set_testing_disk_space(&FakeDiskSpaceMethod); + + BlobDataBuilder file_builder(kFileId); + file_builder.AppendFutureFile(0, kFileBlobSize, 0); + + // When we have < kFileBlobSize, then we cancel our request. + SetTestMemoryLimits(&controller); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(file_builder); + + file_quota_result_ = true; + controller.ReserveFileQuota(items, GetFileCreationCallback()); + + EXPECT_TRUE(file_runner_->HasPendingTask()); + set_disk_space(kFileBlobSize - 1); + + RunFileThreadTasks(); + ExpectDiskSpaceCalled(); + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(file_quota_result_); + EXPECT_TRUE(controller.limits().IsDiskSpaceConstrained()); + EXPECT_EQ(0ull, controller.limits().effective_max_disk_space); + + EXPECT_EQ(0ull, controller.disk_usage()); + EXPECT_EQ(0ull, controller.memory_usage()); +} + +TEST_F(BlobMemoryControllerTest, DiskSpaceHitMinAvailable) { + const std::string kFileId = "id2"; + const uint64_t kFileBlobSize = kTestBlobStorageMaxBlobMemorySize; + + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + controller.set_testing_disk_space(&FakeDiskSpaceMethod); + + BlobDataBuilder file_builder(kFileId); + file_builder.AppendFutureFile(0, kFileBlobSize, 0); + // When we have < limits.min_available_external_disk_space(), then we'll + // modify our effective disk space to match our current usage to stop using + // more disk. + + SetTestMemoryLimits(&controller); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(file_builder); + + file_quota_result_ = false; + controller.ReserveFileQuota(items, GetFileCreationCallback()); + + EXPECT_TRUE(file_runner_->HasPendingTask()); + set_disk_space(controller.limits().min_available_external_disk_space() - 1); + + RunFileThreadTasks(); + ExpectDiskSpaceCalled(); + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(file_quota_result_); + EXPECT_TRUE(controller.limits().IsDiskSpaceConstrained()); + EXPECT_EQ(kFileBlobSize, controller.limits().effective_max_disk_space); + + items.clear(); + files_created_.clear(); + + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(0ull, controller.disk_usage()); + EXPECT_EQ(0ull, controller.memory_usage()); +} + +TEST_F(BlobMemoryControllerTest, DiskSpaceBeforeMinAvailable) { + const std::string kFileId = "id2"; + + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + controller.set_testing_disk_space(&FakeDiskSpaceMethod); + + BlobDataBuilder file_builder(kFileId); + file_builder.AppendFutureFile(0, kTestBlobStorageMaxBlobMemorySize, 0); + + // When our desired total disk space is less than we're allowed given the + // minimum disk availability, we shorten the disk space. + SetTestMemoryLimits(&controller); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(file_builder); + + file_quota_result_ = false; + controller.ReserveFileQuota(items, GetFileCreationCallback()); + + EXPECT_TRUE(file_runner_->HasPendingTask()); + set_disk_space(controller.limits().desired_max_disk_space + + controller.limits().min_available_external_disk_space() + 1); + + RunFileThreadTasks(); + ExpectDiskSpaceCalled(); + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(file_quota_result_); + EXPECT_FALSE(controller.limits().IsDiskSpaceConstrained()) + << controller.limits().effective_max_disk_space; + + items.clear(); + files_created_.clear(); + + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(0ull, controller.disk_usage()); + EXPECT_EQ(0ull, controller.memory_usage()); +} + +TEST_F(BlobMemoryControllerTest, DiskSpaceNearMinAvailable) { + const std::string kFileId = "id2"; + + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + controller.set_testing_disk_space(&FakeDiskSpaceMethod); + + BlobDataBuilder file_builder(kFileId); + file_builder.AppendFutureFile(0, kTestBlobStorageMaxBlobMemorySize, 0); + + // When our desired total disk space is less than we're allowed given the + // minimum disk availability, we shorten the disk space. + SetTestMemoryLimits(&controller); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(file_builder); + + file_quota_result_ = false; + controller.ReserveFileQuota(items, GetFileCreationCallback()); + + EXPECT_TRUE(file_runner_->HasPendingTask()); + set_disk_space(controller.limits().desired_max_disk_space + + controller.limits().min_available_external_disk_space() - 1); + + RunFileThreadTasks(); + ExpectDiskSpaceCalled(); + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(file_quota_result_); + EXPECT_TRUE(controller.limits().IsDiskSpaceConstrained()); + EXPECT_EQ(controller.limits().desired_max_disk_space - 1, + controller.limits().effective_max_disk_space); + + items.clear(); + files_created_.clear(); + + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(0ull, controller.disk_usage()); + EXPECT_EQ(0ull, controller.memory_usage()); +} + +TEST_F(BlobMemoryControllerTest, DiskSpaceResetAfterIncrease) { + const std::string kFileId = "id2"; + const uint64_t kFileBlobSize = kTestBlobStorageMaxBlobMemorySize; + + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + controller.set_testing_disk_space(&FakeDiskSpaceMethod); + + BlobDataBuilder file_builder(kFileId); + file_builder.AppendFutureFile(0, kFileBlobSize, 0); + + // When we do a file operation after disk has been freed (after we've been + // limited), our effective size grows correctly. + SetTestMemoryLimits(&controller); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(file_builder); + + controller.ReserveFileQuota(items, GetFileCreationCallback()); + + EXPECT_TRUE(file_runner_->HasPendingTask()); + set_disk_space(controller.limits().min_available_external_disk_space() - 1); + + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + + // Check the effective limit is constrained. + EXPECT_TRUE(controller.limits().IsDiskSpaceConstrained()); + EXPECT_EQ(kFileBlobSize, controller.limits().effective_max_disk_space); + + // Delete the item so we have disk quota. + items.clear(); + files_created_.clear(); + + RunFileThreadTasks(); + ExpectDiskSpaceCalled(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(0ull, controller.disk_usage()); + EXPECT_EQ(0ull, controller.memory_usage()); + + // Create the same item, but have the disk space report the minimum amount + // needed to have the desired disk size. + items = CreateSharedDataItems(file_builder); + + controller.ReserveFileQuota(items, GetFileCreationCallback()); + + EXPECT_TRUE(file_runner_->HasPendingTask()); + set_disk_space(kTestBlobStorageMaxDiskSpace + + controller.limits().min_available_external_disk_space()); + + RunFileThreadTasks(); + ExpectDiskSpaceCalled(); + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(controller.limits().IsDiskSpaceConstrained()); + EXPECT_EQ(controller.limits().desired_max_disk_space, + controller.limits().effective_max_disk_space); + + items.clear(); + files_created_.clear(); + + RunFileThreadTasks(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(0ull, controller.disk_usage()); + EXPECT_EQ(0ull, controller.memory_usage()); +} + +TEST_F(BlobMemoryControllerTest, DiskSpaceUnknown) { + const std::string kFileId = "id2"; + const uint64_t kFileBlobSize = kTestBlobStorageMaxBlobMemorySize; + + BlobMemoryController controller(temp_dir_.GetPath(), file_runner_); + controller.set_testing_disk_space(&FakeDiskSpaceMethod); + + BlobDataBuilder file_builder(kFileId); + file_builder.AppendFutureFile(0, kFileBlobSize, 0); + + // If the disk space returns an error (-1), then we ignore that signal. + SetTestMemoryLimits(&controller); + + std::vector<scoped_refptr<ShareableBlobDataItem>> items = + CreateSharedDataItems(file_builder); + + controller.ReserveFileQuota(items, GetFileCreationCallback()); + + EXPECT_TRUE(file_runner_->HasPendingTask()); + set_disk_space(-1ll); + + RunFileThreadTasks(); + ExpectDiskSpaceCalled(); + base::RunLoop().RunUntilIdle(); + + // Check the effective limit is constrained. + EXPECT_FALSE(controller.limits().IsDiskSpaceConstrained()); +} + +} // namespace storage diff --git a/chromium/storage/browser/blob/blob_reader_unittest.cc b/chromium/storage/browser/blob/blob_reader_unittest.cc new file mode 100644 index 00000000000..d1909ef8af8 --- /dev/null +++ b/chromium/storage/browser/blob/blob_reader_unittest.cc @@ -0,0 +1,1249 @@ +// Copyright 2015 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 <stddef.h> +#include <stdint.h> +#include <string.h> + +#include <memory> +#include <utility> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "base/task_runner.h" +#include "base/time/time.h" +#include "net/base/completion_callback.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/disk_cache/disk_cache.h" +#include "storage/browser/blob/blob_data_builder.h" +#include "storage/browser/blob/blob_data_handle.h" +#include "storage/browser/blob/blob_reader.h" +#include "storage/browser/blob/blob_storage_context.h" +#include "storage/browser/fileapi/file_stream_reader.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_file_util.h" +#include "storage/browser/test/async_file_test_helper.h" +#include "storage/browser/test/test_file_system_context.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +using base::FilePath; +using content::AsyncFileTestHelper; +using net::DrainableIOBuffer; +using net::IOBuffer; +using FileCreationInfo = storage::BlobMemoryController::FileCreationInfo; + +namespace storage { +namespace { + +const int kTestDiskCacheStreamIndex = 0; +const int kTestDiskCacheSideStreamIndex = 1; + +void SaveBlobStatusAndFiles(BlobStatus* status_ptr, + std::vector<FileCreationInfo>* files_ptr, + BlobStatus status, + std::vector<FileCreationInfo> files) { + *status_ptr = status; + for (FileCreationInfo& info : files) { + files_ptr->push_back(std::move(info)); + } +} + +// Our disk cache tests don't need a real data handle since the tests themselves +// scope the disk cache and entries. +class EmptyDataHandle : public storage::BlobDataBuilder::DataHandle { + private: + ~EmptyDataHandle() override {} +}; + +// A disk_cache::Entry that arbitrarily delays the completion of a read +// operation to allow testing some races without flake. This is particularly +// relevant in this unit test, which uses the always-synchronous MEMORY_CACHE. +class DelayedReadEntry : public disk_cache::Entry { + public: + explicit DelayedReadEntry(disk_cache::ScopedEntryPtr entry) + : entry_(std::move(entry)) {} + ~DelayedReadEntry() override { EXPECT_FALSE(HasPendingReadCallbacks()); } + + bool HasPendingReadCallbacks() { return !pending_read_callbacks_.empty(); } + + void RunPendingReadCallbacks() { + std::vector<base::Callback<void(void)>> callbacks; + pending_read_callbacks_.swap(callbacks); + for (const auto& callback : callbacks) + callback.Run(); + } + + // From disk_cache::Entry: + void Doom() override { entry_->Doom(); } + + void Close() override { delete this; } // Note this is required by the API. + + std::string GetKey() const override { return entry_->GetKey(); } + + base::Time GetLastUsed() const override { return entry_->GetLastUsed(); } + + base::Time GetLastModified() const override { + return entry_->GetLastModified(); + } + + int32_t GetDataSize(int index) const override { + return entry_->GetDataSize(index); + } + + int ReadData(int index, + int offset, + IOBuffer* buf, + int buf_len, + const CompletionCallback& original_callback) override { + net::TestCompletionCallback callback; + int rv = entry_->ReadData(index, offset, buf, buf_len, callback.callback()); + DCHECK_NE(rv, net::ERR_IO_PENDING) + << "Test expects to use a MEMORY_CACHE instance, which is synchronous."; + pending_read_callbacks_.push_back(base::Bind(original_callback, rv)); + return net::ERR_IO_PENDING; + } + + int WriteData(int index, + int offset, + IOBuffer* buf, + int buf_len, + const CompletionCallback& callback, + bool truncate) override { + return entry_->WriteData(index, offset, buf, buf_len, callback, truncate); + } + + int ReadSparseData(int64_t offset, + IOBuffer* buf, + int buf_len, + const CompletionCallback& callback) override { + return entry_->ReadSparseData(offset, buf, buf_len, callback); + } + + int WriteSparseData(int64_t offset, + IOBuffer* buf, + int buf_len, + const CompletionCallback& callback) override { + return entry_->WriteSparseData(offset, buf, buf_len, callback); + } + + int GetAvailableRange(int64_t offset, + int len, + int64_t* start, + const CompletionCallback& callback) override { + return entry_->GetAvailableRange(offset, len, start, callback); + } + + bool CouldBeSparse() const override { return entry_->CouldBeSparse(); } + + void CancelSparseIO() override { entry_->CancelSparseIO(); } + + int ReadyForSparseIO(const CompletionCallback& callback) override { + return entry_->ReadyForSparseIO(callback); + } + + private: + disk_cache::ScopedEntryPtr entry_; + std::vector<base::Callback<void(void)>> pending_read_callbacks_; +}; + +std::unique_ptr<disk_cache::Backend> CreateInMemoryDiskCache( + const scoped_refptr<base::SingleThreadTaskRunner>& thread) { + std::unique_ptr<disk_cache::Backend> cache; + net::TestCompletionCallback callback; + int rv = disk_cache::CreateCacheBackend( + net::MEMORY_CACHE, net::CACHE_BACKEND_DEFAULT, FilePath(), 0, false, + thread, nullptr, &cache, callback.callback()); + EXPECT_EQ(net::OK, callback.GetResult(rv)); + + return cache; +} + +disk_cache::ScopedEntryPtr CreateDiskCacheEntry(disk_cache::Backend* cache, + const char* key, + const std::string& data) { + disk_cache::Entry* temp_entry = nullptr; + net::TestCompletionCallback callback; + int rv = cache->CreateEntry(key, &temp_entry, callback.callback()); + if (callback.GetResult(rv) != net::OK) + return nullptr; + disk_cache::ScopedEntryPtr entry(temp_entry); + + scoped_refptr<net::StringIOBuffer> iobuffer = new net::StringIOBuffer(data); + rv = entry->WriteData(kTestDiskCacheStreamIndex, 0, iobuffer.get(), + iobuffer->size(), callback.callback(), false); + EXPECT_EQ(static_cast<int>(data.size()), callback.GetResult(rv)); + return entry; +} + +disk_cache::ScopedEntryPtr CreateDiskCacheEntryWithSideData( + disk_cache::Backend* cache, + const char* key, + const std::string& data, + const std::string& side_data) { + disk_cache::ScopedEntryPtr entry = CreateDiskCacheEntry(cache, key, data); + scoped_refptr<net::StringIOBuffer> iobuffer = + new net::StringIOBuffer(side_data); + net::TestCompletionCallback callback; + int rv = entry->WriteData(kTestDiskCacheSideStreamIndex, 0, iobuffer.get(), + iobuffer->size(), callback.callback(), false); + EXPECT_EQ(static_cast<int>(side_data.size()), callback.GetResult(rv)); + return entry; +} + +template <typename T> +void SetValue(T* address, T value) { + *address = value; +} + +class FakeFileStreamReader : public FileStreamReader { + public: + explicit FakeFileStreamReader(const std::string& contents) + : buffer_(new DrainableIOBuffer( + new net::StringIOBuffer( + std::unique_ptr<std::string>(new std::string(contents))), + contents.size())), + net_error_(net::OK), + size_(contents.size()) {} + FakeFileStreamReader(const std::string& contents, uint64_t size) + : buffer_(new DrainableIOBuffer( + new net::StringIOBuffer( + std::unique_ptr<std::string>(new std::string(contents))), + contents.size())), + net_error_(net::OK), + size_(size) {} + + ~FakeFileStreamReader() override {} + + void SetReturnError(int net_error) { net_error_ = net_error; } + + void SetAsyncRunner(base::SingleThreadTaskRunner* runner) { + async_task_runner_ = runner; + } + + int Read(net::IOBuffer* buf, + int buf_length, + const net::CompletionCallback& done) override { + DCHECK(buf); + // When async_task_runner_ is not set, return synchronously. + if (!async_task_runner_.get()) { + if (net_error_ == net::OK) { + return ReadImpl(buf, buf_length, net::CompletionCallback()); + } else { + return net_error_; + } + } + + // Otherwise always return asynchronously. + if (net_error_ == net::OK) { + async_task_runner_->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult(&FakeFileStreamReader::ReadImpl), + base::Unretained(this), make_scoped_refptr(buf), + buf_length, done)); + } else { + async_task_runner_->PostTask(FROM_HERE, base::Bind(done, net_error_)); + } + return net::ERR_IO_PENDING; + } + + int64_t GetLength( + const net::Int64CompletionCallback& size_callback) override { + // When async_task_runner_ is not set, return synchronously. + if (!async_task_runner_.get()) { + if (net_error_ == net::OK) { + return size_; + } else { + return net_error_; + } + } + if (net_error_ == net::OK) { + async_task_runner_->PostTask(FROM_HERE, base::Bind(size_callback, size_)); + } else { + async_task_runner_->PostTask( + FROM_HERE, + base::Bind(size_callback, static_cast<int64_t>(net_error_))); + } + return net::ERR_IO_PENDING; + } + + private: + int ReadImpl(scoped_refptr<net::IOBuffer> buf, + int buf_length, + const net::CompletionCallback& done) { + CHECK_GE(buf_length, 0); + int length = std::min(buf_length, buffer_->BytesRemaining()); + memcpy(buf->data(), buffer_->data(), length); + buffer_->DidConsume(length); + if (done.is_null()) { + return length; + } + done.Run(length); + return net::ERR_IO_PENDING; + } + + scoped_refptr<net::DrainableIOBuffer> buffer_; + scoped_refptr<base::SingleThreadTaskRunner> async_task_runner_; + int net_error_; + uint64_t size_; + + DISALLOW_COPY_AND_ASSIGN(FakeFileStreamReader); +}; + +class MockFileStreamReaderProvider + : public BlobReader::FileStreamReaderProvider { + public: + ~MockFileStreamReaderProvider() override {} + + MOCK_METHOD4(CreateForLocalFileMock, + FileStreamReader*(base::TaskRunner* task_runner, + const FilePath& file_path, + int64_t initial_offset, + const base::Time& expected_modification_time)); + MOCK_METHOD4(CreateFileStreamReaderMock, + FileStreamReader*(const GURL& filesystem_url, + int64_t offset, + int64_t max_bytes_to_read, + const base::Time& expected_modification_time)); + // Since we're returning a move-only type, we have to do some delegation for + // gmock. + std::unique_ptr<FileStreamReader> CreateForLocalFile( + base::TaskRunner* task_runner, + const base::FilePath& file_path, + int64_t initial_offset, + const base::Time& expected_modification_time) override { + return base::WrapUnique(CreateForLocalFileMock( + task_runner, file_path, initial_offset, expected_modification_time)); + } + + std::unique_ptr<FileStreamReader> CreateFileStreamReader( + const GURL& filesystem_url, + int64_t offset, + int64_t max_bytes_to_read, + const base::Time& expected_modification_time) override { + return base::WrapUnique(CreateFileStreamReaderMock( + filesystem_url, offset, max_bytes_to_read, expected_modification_time)); + } +}; + +} // namespace + +class BlobReaderTest : public ::testing::Test { + public: + BlobReaderTest() {} + ~BlobReaderTest() override {} + + void TearDown() override { + reader_.reset(); + blob_handle_.reset(); + base::RunLoop().RunUntilIdle(); + base::RunLoop().RunUntilIdle(); + } + + protected: + void InitializeReader(BlobDataBuilder* builder) { + blob_handle_ = builder ? context_.AddFinishedBlob(builder) : nullptr; + provider_ = new MockFileStreamReaderProvider(); + reader_.reset(new BlobReader(blob_handle_.get(), + base::WrapUnique(provider_), + message_loop_.task_runner().get())); + } + + // Takes ownership of the file reader (the blob reader takes ownership). + void ExpectLocalFileCall(const FilePath& file_path, + base::Time modification_time, + uint64_t initial_offset, + FakeFileStreamReader* reader) { + EXPECT_CALL(*provider_, CreateForLocalFileMock( + message_loop_.task_runner().get(), file_path, + initial_offset, modification_time)) + .WillOnce(testing::Return(reader)); + } + + // Takes ownership of the file reader (the blob reader takes ownership). + void ExpectFileSystemCall(const GURL& filesystem_url, + int64_t offset, + int64_t max_bytes_to_read, + base::Time expected_modification_time, + FakeFileStreamReader* reader) { + EXPECT_CALL(*provider_, CreateFileStreamReaderMock( + filesystem_url, offset, max_bytes_to_read, + expected_modification_time)) + .WillOnce(testing::Return(reader)); + } + + void CheckSizeCalculatedSynchronously(size_t expected_size, int async_size) { + EXPECT_EQ(-1, async_size); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(expected_size, reader_->total_size()); + EXPECT_TRUE(reader_->total_size_calculated()); + } + + void CheckSizeNotCalculatedYet(int async_size) { + EXPECT_EQ(-1, async_size); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_FALSE(reader_->total_size_calculated()); + } + + void CheckSizeCalculatedAsynchronously(size_t expected_size, + int async_result) { + EXPECT_EQ(net::OK, async_result); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(expected_size, reader_->total_size()); + EXPECT_TRUE(reader_->total_size_calculated()); + } + + scoped_refptr<net::IOBuffer> CreateBuffer(uint64_t size) { + return scoped_refptr<net::IOBuffer>( + new net::IOBuffer(static_cast<size_t>(size))); + } + + bool IsReaderTotalSizeCalculated() { + return reader_->total_size_calculated(); + } + + BlobStorageContext context_; + std::unique_ptr<BlobDataHandle> blob_handle_; + MockFileStreamReaderProvider* provider_ = nullptr; + base::MessageLoop message_loop_; + std::unique_ptr<BlobReader> reader_; + + private: + DISALLOW_COPY_AND_ASSIGN(BlobReaderTest); +}; + +TEST_F(BlobReaderTest, BasicMemory) { + BlobDataBuilder b("uuid"); + const std::string kData("Hello!!!"); + const size_t kDataSize = 8ul; + b.AppendData(kData); + this->InitializeReader(&b); + + int size_result = -1; + EXPECT_FALSE(IsReaderTotalSizeCalculated()); + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + EXPECT_TRUE(reader_->IsInMemory()); + CheckSizeCalculatedSynchronously(kDataSize, size_result); + + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kDataSize)); + + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->Read(buffer.get(), kDataSize, &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(kDataSize, static_cast<size_t>(bytes_read)); + EXPECT_EQ(0, async_bytes_read); + EXPECT_EQ(0, memcmp(buffer->data(), "Hello!!!", kDataSize)); +} + +TEST_F(BlobReaderTest, BasicFile) { + BlobDataBuilder b("uuid"); + const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt"); + const std::string kData = "FileData!!!"; + const base::Time kTime = base::Time::Now(); + b.AppendFile(kPath, 0, kData.size(), kTime); + this->InitializeReader(&b); + + // Non-async reader. + ExpectLocalFileCall(kPath, kTime, 0, new FakeFileStreamReader(kData)); + + int size_result = -1; + EXPECT_FALSE(IsReaderTotalSizeCalculated()); + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + EXPECT_FALSE(reader_->IsInMemory()); + CheckSizeCalculatedSynchronously(kData.size(), size_result); + + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size())); + + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->Read(buffer.get(), kData.size(), &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(kData.size(), static_cast<size_t>(bytes_read)); + EXPECT_EQ(0, async_bytes_read); + EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size())); +} + +TEST_F(BlobReaderTest, BasicFileSystem) { + BlobDataBuilder b("uuid"); + const GURL kURL("file://test_file/here.txt"); + const std::string kData = "FileData!!!"; + const base::Time kTime = base::Time::Now(); + b.AppendFileSystemFile(kURL, 0, kData.size(), kTime); + this->InitializeReader(&b); + // Non-async reader. + ExpectFileSystemCall(kURL, 0, kData.size(), kTime, + new FakeFileStreamReader(kData)); + + int size_result = -1; + EXPECT_FALSE(IsReaderTotalSizeCalculated()); + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + EXPECT_FALSE(reader_->IsInMemory()); + + CheckSizeCalculatedSynchronously(kData.size(), size_result); + + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size())); + + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->Read(buffer.get(), kData.size(), &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(kData.size(), static_cast<size_t>(bytes_read)); + EXPECT_EQ(0, async_bytes_read); + EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size())); +} + +TEST_F(BlobReaderTest, BasicDiskCache) { + std::unique_ptr<disk_cache::Backend> cache = + CreateInMemoryDiskCache(message_loop_.task_runner()); + ASSERT_TRUE(cache); + + BlobDataBuilder b("uuid"); + const std::string kData = "Test Blob Data"; + scoped_refptr<BlobDataBuilder::DataHandle> data_handle = + new EmptyDataHandle(); + disk_cache::ScopedEntryPtr entry = + CreateDiskCacheEntry(cache.get(), "test entry", kData); + b.AppendDiskCacheEntry(data_handle, entry.get(), kTestDiskCacheStreamIndex); + this->InitializeReader(&b); + + int size_result = -1; + EXPECT_FALSE(IsReaderTotalSizeCalculated()); + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + CheckSizeCalculatedSynchronously(kData.size(), size_result); + + EXPECT_FALSE(reader_->has_side_data()); + + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size())); + + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->Read(buffer.get(), kData.size(), &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(kData.size(), static_cast<size_t>(bytes_read)); + EXPECT_EQ(0, async_bytes_read); + EXPECT_EQ(0, memcmp(buffer->data(), "Test Blob Data", kData.size())); +} + +TEST_F(BlobReaderTest, DiskCacheWithSideData) { + std::unique_ptr<disk_cache::Backend> cache = + CreateInMemoryDiskCache(message_loop_.task_runner()); + ASSERT_TRUE(cache); + + BlobDataBuilder b("uuid"); + const std::string kData = "Test Blob Data"; + const std::string kSideData = "Test side data"; + scoped_refptr<BlobDataBuilder::DataHandle> data_handle = + new EmptyDataHandle(); + disk_cache::ScopedEntryPtr entry = CreateDiskCacheEntryWithSideData( + cache.get(), "test entry", kData, kSideData); + b.AppendDiskCacheEntryWithSideData(data_handle, entry.get(), + kTestDiskCacheStreamIndex, + kTestDiskCacheSideStreamIndex); + this->InitializeReader(&b); + + int size_result = -1; + EXPECT_FALSE(IsReaderTotalSizeCalculated()); + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + CheckSizeCalculatedSynchronously(kData.size(), size_result); + + EXPECT_TRUE(reader_->has_side_data()); + BlobReader::Status status = BlobReader::Status::DONE; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->ReadSideData( + base::Bind(&SetValue<BlobReader::Status>, &status))); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_TRUE(reader_->side_data()); + std::string result(reader_->side_data()->data(), + reader_->side_data()->size()); + EXPECT_EQ(kSideData, result); +} + +TEST_F(BlobReaderTest, BufferLargerThanMemory) { + BlobDataBuilder b("uuid"); + const std::string kData("Hello!!!"); + const size_t kDataSize = 8ul; + const size_t kBufferSize = 10ul; + b.AppendData(kData); + this->InitializeReader(&b); + + int size_result = -1; + EXPECT_FALSE(IsReaderTotalSizeCalculated()); + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + CheckSizeCalculatedSynchronously(kData.size(), size_result); + + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize)); + + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->Read(buffer.get(), kBufferSize, &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(kDataSize, static_cast<size_t>(bytes_read)); + EXPECT_EQ(0, async_bytes_read); + EXPECT_EQ(0, memcmp(buffer->data(), "Hello!!!", kDataSize)); +} + +TEST_F(BlobReaderTest, MemoryRange) { + BlobDataBuilder b("uuid"); + const std::string kData("Hello!!!"); + const size_t kDataSize = 8ul; + const size_t kSeekOffset = 2ul; + const uint64_t kReadLength = 4ull; + b.AppendData(kData); + this->InitializeReader(&b); + + int size_result = -1; + EXPECT_FALSE(IsReaderTotalSizeCalculated()); + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + CheckSizeCalculatedSynchronously(kData.size(), size_result); + + scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kReadLength); + + reader_->SetReadRange(kSeekOffset, kReadLength); + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->Read(buffer.get(), kDataSize - kSeekOffset, &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(kReadLength, static_cast<size_t>(bytes_read)); + EXPECT_EQ(0, async_bytes_read); + EXPECT_EQ(0, memcmp(buffer->data(), "llo!", kReadLength)); +} + +TEST_F(BlobReaderTest, BufferSmallerThanMemory) { + BlobDataBuilder b("uuid"); + const std::string kData("Hello!!!"); + const size_t kBufferSize = 4ul; + b.AppendData(kData); + this->InitializeReader(&b); + + int size_result = -1; + EXPECT_FALSE(IsReaderTotalSizeCalculated()); + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + CheckSizeCalculatedSynchronously(kData.size(), size_result); + + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize)); + + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->Read(buffer.get(), kBufferSize, &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(kBufferSize, static_cast<size_t>(bytes_read)); + EXPECT_EQ(0, async_bytes_read); + EXPECT_EQ(0, memcmp(buffer->data(), "Hell", kBufferSize)); + + bytes_read = 0; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->Read(buffer.get(), kBufferSize, &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(kBufferSize, static_cast<size_t>(bytes_read)); + EXPECT_EQ(0, async_bytes_read); + EXPECT_EQ(0, memcmp(buffer->data(), "o!!!", kBufferSize)); +} + +TEST_F(BlobReaderTest, SegmentedBufferAndMemory) { + BlobDataBuilder b("uuid"); + const size_t kNumItems = 10; + const size_t kItemSize = 6; + const size_t kBufferSize = 10; + const size_t kTotalSize = kNumItems * kItemSize; + char current_value = 0; + for (size_t i = 0; i < kNumItems; i++) { + char buf[kItemSize]; + for (size_t j = 0; j < kItemSize; j++) { + buf[j] = current_value++; + } + b.AppendData(buf, kItemSize); + } + this->InitializeReader(&b); + + int size_result = -1; + EXPECT_FALSE(IsReaderTotalSizeCalculated()); + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + CheckSizeCalculatedSynchronously(kTotalSize, size_result); + + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize)); + + current_value = 0; + for (size_t i = 0; i < kTotalSize / kBufferSize; i++) { + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->Read(buffer.get(), kBufferSize, &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(kBufferSize, static_cast<size_t>(bytes_read)); + EXPECT_EQ(0, async_bytes_read); + for (size_t j = 0; j < kBufferSize; j++) { + EXPECT_EQ(current_value, buffer->data()[j]); + current_value++; + } + } +} + +TEST_F(BlobReaderTest, FileAsync) { + BlobDataBuilder b("uuid"); + const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt"); + const std::string kData = "FileData!!!"; + const base::Time kTime = base::Time::Now(); + b.AppendFile(kPath, 0, kData.size(), kTime); + this->InitializeReader(&b); + + std::unique_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(kData)); + reader->SetAsyncRunner(message_loop_.task_runner().get()); + + ExpectLocalFileCall(kPath, kTime, 0, reader.release()); + + int size_result = -1; + EXPECT_FALSE(IsReaderTotalSizeCalculated()); + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + EXPECT_FALSE(reader_->IsInMemory()); + CheckSizeNotCalculatedYet(size_result); + base::RunLoop().RunUntilIdle(); + CheckSizeCalculatedAsynchronously(kData.size(), size_result); + + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size())); + + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->Read(buffer.get(), kData.size(), &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(kData.size(), static_cast<size_t>(async_bytes_read)); + EXPECT_EQ(0, bytes_read); + EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size())); +} + +TEST_F(BlobReaderTest, FileSystemAsync) { + BlobDataBuilder b("uuid"); + const GURL kURL("file://test_file/here.txt"); + const std::string kData = "FileData!!!"; + const base::Time kTime = base::Time::Now(); + b.AppendFileSystemFile(kURL, 0, kData.size(), kTime); + this->InitializeReader(&b); + + std::unique_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(kData)); + reader->SetAsyncRunner(message_loop_.task_runner().get()); + + ExpectFileSystemCall(kURL, 0, kData.size(), kTime, reader.release()); + + int size_result = -1; + EXPECT_FALSE(IsReaderTotalSizeCalculated()); + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + CheckSizeNotCalculatedYet(size_result); + EXPECT_FALSE(reader_->IsInMemory()); + base::RunLoop().RunUntilIdle(); + CheckSizeCalculatedAsynchronously(kData.size(), size_result); + + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size())); + + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->Read(buffer.get(), kData.size(), &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(kData.size(), static_cast<size_t>(async_bytes_read)); + EXPECT_EQ(0, bytes_read); + EXPECT_EQ(0, memcmp(buffer->data(), "FileData!!!", kData.size())); +} + +TEST_F(BlobReaderTest, DiskCacheAsync) { + std::unique_ptr<disk_cache::Backend> cache = + CreateInMemoryDiskCache(message_loop_.task_runner()); + ASSERT_TRUE(cache); + + BlobDataBuilder b("uuid"); + const std::string kData = "Test Blob Data"; + scoped_refptr<BlobDataBuilder::DataHandle> data_handle = + new EmptyDataHandle(); + std::unique_ptr<DelayedReadEntry> delayed_read_entry(new DelayedReadEntry( + CreateDiskCacheEntry(cache.get(), "test entry", kData))); + b.AppendDiskCacheEntry(data_handle, delayed_read_entry.get(), + kTestDiskCacheStreamIndex); + this->InitializeReader(&b); + + int size_result = -1; + EXPECT_FALSE(IsReaderTotalSizeCalculated()); + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + CheckSizeCalculatedSynchronously(kData.size(), size_result); + + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size())); + + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->Read(buffer.get(), kData.size(), &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_TRUE(delayed_read_entry->HasPendingReadCallbacks()); + delayed_read_entry->RunPendingReadCallbacks(); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(0, bytes_read); + EXPECT_EQ(kData.size(), static_cast<size_t>(async_bytes_read)); + EXPECT_EQ(0, memcmp(buffer->data(), "Test Blob Data", kData.size())); +} + +TEST_F(BlobReaderTest, FileRange) { + BlobDataBuilder b("uuid"); + const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt"); + // We check the offset in the ExpectLocalFileCall mock. + const std::string kRangeData = "leD"; + const std::string kData = "FileData!!!"; + const uint64_t kOffset = 2; + const uint64_t kReadLength = 3; + const base::Time kTime = base::Time::Now(); + b.AppendFile(kPath, 0, kData.size(), kTime); + this->InitializeReader(&b); + + std::unique_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader(kData)); + reader->SetAsyncRunner(message_loop_.task_runner().get()); + ExpectLocalFileCall(kPath, kTime, 0, reader.release()); + + // We create the reader again with the offset after the seek. + reader.reset(new FakeFileStreamReader(kRangeData)); + reader->SetAsyncRunner(message_loop_.task_runner().get()); + ExpectLocalFileCall(kPath, kTime, kOffset, reader.release()); + + int size_result = -1; + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + base::RunLoop().RunUntilIdle(); + + scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kReadLength); + EXPECT_EQ(BlobReader::Status::DONE, + reader_->SetReadRange(kOffset, kReadLength)); + + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->Read(buffer.get(), kReadLength, &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(kReadLength, static_cast<size_t>(async_bytes_read)); + EXPECT_EQ(0, bytes_read); + EXPECT_EQ(0, memcmp(buffer->data(), "leD", kReadLength)); +} + +TEST_F(BlobReaderTest, DiskCacheRange) { + std::unique_ptr<disk_cache::Backend> cache = + CreateInMemoryDiskCache(message_loop_.task_runner()); + ASSERT_TRUE(cache); + + BlobDataBuilder b("uuid"); + const std::string kData = "Test Blob Data"; + const uint64_t kOffset = 2; + const uint64_t kReadLength = 3; + scoped_refptr<BlobDataBuilder::DataHandle> data_handle = + new EmptyDataHandle(); + disk_cache::ScopedEntryPtr entry = + CreateDiskCacheEntry(cache.get(), "test entry", kData); + b.AppendDiskCacheEntry(data_handle, entry.get(), kTestDiskCacheStreamIndex); + this->InitializeReader(&b); + + int size_result = -1; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + + scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kReadLength); + EXPECT_EQ(BlobReader::Status::DONE, + reader_->SetReadRange(kOffset, kReadLength)); + + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->Read(buffer.get(), kReadLength, &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(kReadLength, static_cast<size_t>(bytes_read)); + EXPECT_EQ(0, async_bytes_read); + EXPECT_EQ(0, memcmp(buffer->data(), "st ", kReadLength)); +} + +TEST_F(BlobReaderTest, FileSomeAsyncSegmentedOffsetsUnknownSizes) { + // This tests includes: + // * Unknown file sizes (item length of uint64_t::max) for every other item. + // * Offsets for every 3rd file item. + // * Non-async reader for every 4th file item. + BlobDataBuilder b("uuid"); + const FilePath kPathBase = FilePath::FromUTF8Unsafe("/fake/file.txt"); + const base::Time kTime = base::Time::Now(); + const size_t kNumItems = 10; + const size_t kItemSize = 6; + const size_t kBufferSize = 10; + const size_t kTotalSize = kNumItems * kItemSize; + char current_value = 0; + // Create blob and reader. + for (size_t i = 0; i < kNumItems; i++) { + current_value += kItemSize; + FilePath path = kPathBase.Append( + FilePath::FromUTF8Unsafe(base::StringPrintf("%d", current_value))); + uint64_t offset = i % 3 == 0 ? 1 : 0; + uint64_t size = kItemSize; + b.AppendFile(path, offset, size, kTime); + } + this->InitializeReader(&b); + + // Set expectations. + current_value = 0; + for (size_t i = 0; i < kNumItems; i++) { + uint64_t offset = i % 3 == 0 ? 1 : 0; + std::unique_ptr<char[]> buf(new char[kItemSize + offset]); + if (offset > 0) { + memset(buf.get(), 7, offset); + } + for (size_t j = 0; j < kItemSize; j++) { + buf.get()[j + offset] = current_value++; + } + std::unique_ptr<FakeFileStreamReader> reader(new FakeFileStreamReader( + std::string(buf.get() + offset, kItemSize), kItemSize + offset)); + if (i % 4 != 0) { + reader->SetAsyncRunner(message_loop_.task_runner().get()); + } + FilePath path = kPathBase.Append( + FilePath::FromUTF8Unsafe(base::StringPrintf("%d", current_value))); + ExpectLocalFileCall(path, kTime, offset, reader.release()); + } + + int size_result = -1; + EXPECT_FALSE(IsReaderTotalSizeCalculated()); + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + CheckSizeNotCalculatedYet(size_result); + base::RunLoop().RunUntilIdle(); + CheckSizeCalculatedAsynchronously(kTotalSize, size_result); + + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize)); + + current_value = 0; + for (size_t i = 0; i < kTotalSize / kBufferSize; i++) { + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->Read(buffer.get(), kBufferSize, &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(0, bytes_read); + EXPECT_EQ(kBufferSize, static_cast<size_t>(async_bytes_read)); + for (size_t j = 0; j < kBufferSize; j++) { + EXPECT_EQ(current_value, buffer->data()[j]); + current_value++; + } + } +} + +TEST_F(BlobReaderTest, MixedContent) { + // Includes data, a file, and a disk cache entry. + std::unique_ptr<disk_cache::Backend> cache = + CreateInMemoryDiskCache(message_loop_.task_runner()); + ASSERT_TRUE(cache); + + BlobDataBuilder b("uuid"); + const std::string kData1("Hello "); + const std::string kData2("there. "); + const std::string kData3("This "); + const std::string kData4("is multi-content."); + const uint64_t kDataSize = 35; + + const base::Time kTime = base::Time::Now(); + const FilePath kData1Path = FilePath::FromUTF8Unsafe("/fake/file.txt"); + + disk_cache::ScopedEntryPtr entry3 = + CreateDiskCacheEntry(cache.get(), "test entry", kData3); + + b.AppendFile(kData1Path, 0, kData1.size(), kTime); + b.AppendData(kData2); + b.AppendDiskCacheEntry( + scoped_refptr<BlobDataBuilder::DataHandle>(new EmptyDataHandle()), + entry3.get(), kTestDiskCacheStreamIndex); + b.AppendData(kData4); + + this->InitializeReader(&b); + + std::unique_ptr<FakeFileStreamReader> reader( + new FakeFileStreamReader(kData1)); + reader->SetAsyncRunner(message_loop_.task_runner().get()); + ExpectLocalFileCall(kData1Path, kTime, 0, reader.release()); + + int size_result = -1; + EXPECT_FALSE(IsReaderTotalSizeCalculated()); + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + CheckSizeNotCalculatedYet(size_result); + base::RunLoop().RunUntilIdle(); + CheckSizeCalculatedAsynchronously(kDataSize, size_result); + + scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kDataSize); + + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->Read(buffer.get(), kDataSize, &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(0, async_bytes_read); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(0, bytes_read); + EXPECT_EQ(kDataSize, static_cast<size_t>(async_bytes_read)); + EXPECT_EQ(0, memcmp(buffer->data(), "Hello there. This is multi-content.", + kDataSize)); +} + +TEST_F(BlobReaderTest, StateErrors) { + // Test common variables + int bytes_read = -1; + int async_bytes_read = -1; + int size_result = -1; + const std::string kData("Hello!!!"); + + // Case: Blob handle is a nullptr. + InitializeReader(nullptr); + EXPECT_EQ(BlobReader::Status::NET_ERROR, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error()); + EXPECT_EQ(BlobReader::Status::NET_ERROR, reader_->SetReadRange(0, 10)); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error()); + scoped_refptr<net::IOBuffer> buffer = CreateBuffer(10); + EXPECT_EQ(BlobReader::Status::NET_ERROR, + reader_->Read(buffer.get(), 10, &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error()); + + // Case: Not calling CalculateSize before SetReadRange. + BlobDataBuilder builder1("uuid1"); + builder1.AppendData(kData); + InitializeReader(&builder1); + EXPECT_EQ(BlobReader::Status::NET_ERROR, reader_->SetReadRange(0, 10)); + EXPECT_EQ(net::ERR_FAILED, reader_->net_error()); + EXPECT_EQ(BlobReader::Status::NET_ERROR, + reader_->Read(buffer.get(), 10, &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + + // Case: Not calling CalculateSize before Read. + BlobDataBuilder builder2("uuid2"); + builder2.AppendData(kData); + InitializeReader(&builder2); + EXPECT_EQ(BlobReader::Status::NET_ERROR, + reader_->Read(buffer.get(), 10, &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); +} + +TEST_F(BlobReaderTest, FileErrorsSync) { + int size_result = -1; + const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt"); + const std::string kData = "FileData!!!"; + const base::Time kTime = base::Time::Now(); + + // Case: Error on length query. + BlobDataBuilder builder1("uuid1"); + builder1.AppendFile(kPath, 0, kData.size(), kTime); + this->InitializeReader(&builder1); + FakeFileStreamReader* reader = new FakeFileStreamReader(kData); + reader->SetReturnError(net::ERR_FILE_NOT_FOUND); + ExpectLocalFileCall(kPath, kTime, 0, reader); + + EXPECT_EQ(BlobReader::Status::NET_ERROR, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error()); + + // Case: Error on read. + BlobDataBuilder builder2("uuid2"); + builder2.AppendFile(kPath, 0, kData.size(), kTime); + this->InitializeReader(&builder2); + reader = new FakeFileStreamReader(kData); + ExpectLocalFileCall(kPath, kTime, 0, reader); + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + reader->SetReturnError(net::ERR_FILE_NOT_FOUND); + + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size())); + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::NET_ERROR, + reader_->Read(buffer.get(), kData.size(), &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error()); +} + +TEST_F(BlobReaderTest, FileErrorsAsync) { + int size_result = -1; + const FilePath kPath = FilePath::FromUTF8Unsafe("/fake/file.txt"); + const std::string kData = "FileData!!!"; + const base::Time kTime = base::Time::Now(); + + // Case: Error on length query. + BlobDataBuilder builder1("uuid1"); + builder1.AppendFile(kPath, 0, kData.size(), kTime); + this->InitializeReader(&builder1); + FakeFileStreamReader* reader = new FakeFileStreamReader(kData); + reader->SetAsyncRunner(message_loop_.task_runner().get()); + reader->SetReturnError(net::ERR_FILE_NOT_FOUND); + ExpectLocalFileCall(kPath, kTime, 0, reader); + + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + EXPECT_EQ(net::OK, reader_->net_error()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, size_result); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error()); + + // Case: Error on read. + BlobDataBuilder builder2("uuid2"); + builder2.AppendFile(kPath, 0, kData.size(), kTime); + this->InitializeReader(&builder2); + reader = new FakeFileStreamReader(kData); + ExpectLocalFileCall(kPath, kTime, 0, reader); + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + reader->SetReturnError(net::ERR_FILE_NOT_FOUND); + reader->SetAsyncRunner(message_loop_.task_runner().get()); + + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kData.size())); + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->Read(buffer.get(), kData.size(), &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(net::OK, reader_->net_error()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, async_bytes_read); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error()); +} + +TEST_F(BlobReaderTest, RangeError) { + const std::string kData("Hello!!!"); + const size_t kDataSize = 8ul; + const uint64_t kReadLength = 4ull; + + // Case: offset too high. + BlobDataBuilder b("uuid1"); + b.AppendData(kData); + this->InitializeReader(&b); + int size_result = -1; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + scoped_refptr<net::IOBuffer> buffer = CreateBuffer(kDataSize); + EXPECT_EQ(BlobReader::Status::NET_ERROR, + reader_->SetReadRange(kDataSize + 1, kReadLength)); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error()); + + // Case: length too long. + BlobDataBuilder b2("uuid2"); + b2.AppendData(kData); + this->InitializeReader(&b2); + size_result = -1; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + buffer = CreateBuffer(kDataSize + 1); + EXPECT_EQ(BlobReader::Status::NET_ERROR, + reader_->SetReadRange(0, kDataSize + 1)); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, reader_->net_error()); +} + +TEST_F(BlobReaderTest, HandleBeforeAsyncCancel) { + const std::string kUuid("uuid1"); + const std::string kData("Hello!!!"); + const size_t kDataSize = 8ul; + std::vector<FileCreationInfo> files; + + BlobDataBuilder b(kUuid); + b.AppendFutureData(kDataSize); + BlobStatus can_populate_status = + BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; + blob_handle_ = context_.BuildBlob( + b, base::Bind(&SaveBlobStatusAndFiles, &can_populate_status, &files)); + EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, can_populate_status); + provider_ = new MockFileStreamReaderProvider(); + reader_.reset(new BlobReader(blob_handle_.get(), base::WrapUnique(provider_), + message_loop_.task_runner().get())); + int size_result = -1; + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + EXPECT_FALSE(reader_->IsInMemory()); + context_.CancelBuildingBlob(kUuid, + BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(net::ERR_FAILED, size_result); +} + +TEST_F(BlobReaderTest, ReadFromIncompleteBlob) { + const std::string kUuid("uuid1"); + const std::string kData("Hello!!!"); + const size_t kDataSize = 8ul; + std::vector<FileCreationInfo> files; + + BlobDataBuilder b(kUuid); + b.AppendFutureData(kDataSize); + BlobStatus can_populate_status = + BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; + blob_handle_ = context_.BuildBlob( + b, base::Bind(&SaveBlobStatusAndFiles, &can_populate_status, &files)); + EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, can_populate_status); + provider_ = new MockFileStreamReaderProvider(); + reader_.reset(new BlobReader(blob_handle_.get(), base::WrapUnique(provider_), + message_loop_.task_runner().get())); + int size_result = -1; + EXPECT_EQ(BlobReader::Status::IO_PENDING, + reader_->CalculateSize(base::Bind(&SetValue<int>, &size_result))); + EXPECT_FALSE(reader_->IsInMemory()); + b.PopulateFutureData(0, kData.data(), 0, kDataSize); + context_.NotifyTransportComplete(kUuid); + base::RunLoop().RunUntilIdle(); + CheckSizeCalculatedAsynchronously(kDataSize, size_result); + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kDataSize)); + + int bytes_read = 0; + int async_bytes_read = 0; + EXPECT_EQ(BlobReader::Status::DONE, + reader_->Read(buffer.get(), kDataSize, &bytes_read, + base::Bind(&SetValue<int>, &async_bytes_read))); + EXPECT_EQ(net::OK, reader_->net_error()); + EXPECT_EQ(kDataSize, static_cast<size_t>(bytes_read)); + EXPECT_EQ(0, async_bytes_read); + EXPECT_EQ(kData, std::string(buffer->data(), kDataSize)); + EXPECT_EQ(net::OK, size_result); +} + +} // namespace storage diff --git a/chromium/storage/browser/blob/blob_slice_unittest.cc b/chromium/storage/browser/blob/blob_slice_unittest.cc new file mode 100644 index 00000000000..97d2bd05fa3 --- /dev/null +++ b/chromium/storage/browser/blob/blob_slice_unittest.cc @@ -0,0 +1,221 @@ +// Copyright 2016 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 "storage/browser/blob/blob_storage_context.h" + +#include <memory> + +#include "base/files/file_path.h" +#include "storage/browser/blob/blob_data_builder.h" +#include "storage/browser/blob/blob_data_item.h" +#include "storage/browser/blob/blob_entry.h" +#include "storage/browser/blob/shareable_blob_data_item.h" +#include "storage/common/data_element.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace storage { +namespace { +const char kType[] = "type"; +const char kDisposition[] = ""; +} // namespace + +class BlobSliceTest : public testing::Test { + protected: + using BlobSlice = BlobStorageContext::BlobSlice; + + BlobSliceTest() {} + ~BlobSliceTest() override {} + + scoped_refptr<ShareableBlobDataItem> CreateDataItem(size_t size) { + std::unique_ptr<DataElement> element(new DataElement()); + element->SetToAllocatedBytes(size); + for (size_t i = 0; i < size; i++) { + *(element->mutable_bytes() + i) = i; + } + return scoped_refptr<ShareableBlobDataItem>( + new ShareableBlobDataItem(new BlobDataItem(std::move(element)), + ShareableBlobDataItem::QUOTA_NEEDED)); + }; + + scoped_refptr<ShareableBlobDataItem> CreateFileItem(size_t offset, + size_t size) { + std::unique_ptr<DataElement> element(new DataElement()); + element->SetToFilePathRange(base::FilePath(FILE_PATH_LITERAL("kFakePath")), + offset, size, base::Time::Max()); + return scoped_refptr<ShareableBlobDataItem>(new ShareableBlobDataItem( + new BlobDataItem(std::move(element)), + ShareableBlobDataItem::POPULATED_WITHOUT_QUOTA)); + }; + + scoped_refptr<ShareableBlobDataItem> CreateTempFileItem(size_t offset, + size_t size) { + std::unique_ptr<DataElement> element(new DataElement()); + element->SetToFilePathRange(BlobDataBuilder::GetFutureFileItemPath(0), + offset, size, base::Time()); + return scoped_refptr<ShareableBlobDataItem>( + new ShareableBlobDataItem(new BlobDataItem(std::move(element)), + ShareableBlobDataItem::QUOTA_NEEDED)); + }; + + void ExpectFirstSlice(const BlobSlice& slice, + scoped_refptr<ShareableBlobDataItem> source_item, + size_t first_item_slice_offset, + size_t size) { + EXPECT_TRUE(slice.first_source_item); + EXPECT_EQ(first_item_slice_offset, slice.first_item_slice_offset); + + ASSERT_LE(1u, slice.dest_items.size()); + + scoped_refptr<ShareableBlobDataItem> item = slice.dest_items[0]; + EXPECT_EQ(ShareableBlobDataItem::QUOTA_NEEDED, item->state()); + const DataElement& dest_element = item->item()->data_element(); + + EXPECT_EQ(DataElement::TYPE_BYTES_DESCRIPTION, dest_element.type()); + EXPECT_EQ(static_cast<uint64_t>(size), dest_element.length()); + + EXPECT_EQ(*source_item, *slice.first_source_item); + } + + void ExpectLastSlice(const BlobSlice& slice, + scoped_refptr<ShareableBlobDataItem> source_item, + size_t size) { + EXPECT_TRUE(slice.last_source_item); + + ASSERT_LE(2u, slice.dest_items.size()); + scoped_refptr<ShareableBlobDataItem> item = slice.dest_items.back(); + EXPECT_EQ(ShareableBlobDataItem::QUOTA_NEEDED, item->state()); + const DataElement& dest_element = item->item()->data_element(); + + EXPECT_EQ(DataElement::TYPE_BYTES_DESCRIPTION, dest_element.type()); + EXPECT_EQ(static_cast<uint64_t>(size), dest_element.length()); + + EXPECT_EQ(*source_item, *slice.last_source_item); + } +}; + +TEST_F(BlobSliceTest, FullItem) { + const size_t kSize = 5u; + + BlobEntry data(kType, kDisposition); + scoped_refptr<ShareableBlobDataItem> item = CreateDataItem(kSize); + data.AppendSharedBlobItem(item); + + BlobSlice slice(data, 0, 5); + EXPECT_EQ(0u, slice.copying_memory_size.ValueOrDie()); + EXPECT_FALSE(slice.first_source_item); + EXPECT_FALSE(slice.last_source_item); + EXPECT_FALSE(slice.first_source_item); + EXPECT_FALSE(slice.last_source_item); + ASSERT_EQ(1u, slice.dest_items.size()); + EXPECT_EQ(item, slice.dest_items[0]); +} + +TEST_F(BlobSliceTest, SliceSingleItem) { + const size_t kSize = 5u; + + BlobEntry data(kType, kDisposition); + scoped_refptr<ShareableBlobDataItem> item = CreateDataItem(kSize); + data.AppendSharedBlobItem(item); + + BlobSlice slice(data, 1, 3); + EXPECT_EQ(3u, slice.copying_memory_size.ValueOrDie()); + EXPECT_FALSE(slice.last_source_item); + ExpectFirstSlice(slice, item, 1, 3); + ASSERT_EQ(1u, slice.dest_items.size()); +} + +TEST_F(BlobSliceTest, SliceSingleLastItem) { + const size_t kSize1 = 5u; + const size_t kSize2 = 10u; + + BlobEntry data(kType, kDisposition); + scoped_refptr<ShareableBlobDataItem> item1 = CreateDataItem(kSize1); + scoped_refptr<ShareableBlobDataItem> item2 = CreateDataItem(kSize2); + data.AppendSharedBlobItem(item1); + data.AppendSharedBlobItem(item2); + + BlobSlice slice(data, 6, 2); + EXPECT_EQ(2u, slice.copying_memory_size.ValueOrDie()); + ExpectFirstSlice(slice, item2, 1, 2); + ASSERT_EQ(1u, slice.dest_items.size()); +} + +TEST_F(BlobSliceTest, SliceAcrossTwoItems) { + const size_t kSize1 = 5u; + const size_t kSize2 = 10u; + + BlobEntry data(kType, kDisposition); + scoped_refptr<ShareableBlobDataItem> item1 = CreateDataItem(kSize1); + scoped_refptr<ShareableBlobDataItem> item2 = CreateDataItem(kSize2); + data.AppendSharedBlobItem(item1); + data.AppendSharedBlobItem(item2); + + BlobSlice slice(data, 4, 10); + EXPECT_EQ(10u, slice.copying_memory_size.ValueOrDie()); + ExpectFirstSlice(slice, item1, 4, 1); + ExpectLastSlice(slice, item2, 9); + ASSERT_EQ(2u, slice.dest_items.size()); +} + +TEST_F(BlobSliceTest, SliceFileAndLastItem) { + const size_t kSize1 = 5u; + const size_t kSize2 = 10u; + + BlobEntry data(kType, kDisposition); + scoped_refptr<ShareableBlobDataItem> item1 = CreateFileItem(0u, kSize1); + scoped_refptr<ShareableBlobDataItem> item2 = CreateDataItem(kSize2); + data.AppendSharedBlobItem(item1); + data.AppendSharedBlobItem(item2); + + BlobSlice slice(data, 4, 2); + EXPECT_EQ(1u, slice.copying_memory_size.ValueOrDie()); + EXPECT_FALSE(slice.first_source_item); + ExpectLastSlice(slice, item2, 1); + ASSERT_EQ(2u, slice.dest_items.size()); + + EXPECT_EQ(*CreateFileItem(4u, 1u)->item(), *slice.dest_items[0]->item()); +} + +TEST_F(BlobSliceTest, SliceAcrossLargeItem) { + const size_t kSize1 = 5u; + const size_t kSize2 = 10u; + const size_t kSize3 = 10u; + + BlobEntry data(kType, kDisposition); + scoped_refptr<ShareableBlobDataItem> item1 = CreateDataItem(kSize1); + scoped_refptr<ShareableBlobDataItem> item2 = CreateFileItem(0u, kSize2); + scoped_refptr<ShareableBlobDataItem> item3 = CreateDataItem(kSize3); + data.AppendSharedBlobItem(item1); + data.AppendSharedBlobItem(item2); + data.AppendSharedBlobItem(item3); + + BlobSlice slice(data, 2, 20); + EXPECT_EQ(3u + 7u, slice.copying_memory_size.ValueOrDie()); + ExpectFirstSlice(slice, item1, 2, 3); + ExpectLastSlice(slice, item3, 7); + ASSERT_EQ(3u, slice.dest_items.size()); + + EXPECT_EQ(*item2, *slice.dest_items[1]); +} + +TEST_F(BlobSliceTest, SliceTempFileItem) { + BlobEntry data(kType, kDisposition); + scoped_refptr<ShareableBlobDataItem> item1 = CreateTempFileItem(1u, 10u); + data.AppendSharedBlobItem(item1); + BlobSlice slice(data, 2, 5); + EXPECT_EQ(0u, slice.copying_memory_size.ValueOrDie()); + EXPECT_TRUE(slice.first_source_item); + EXPECT_EQ(2u, slice.first_item_slice_offset); + ASSERT_LE(1u, slice.dest_items.size()); + scoped_refptr<ShareableBlobDataItem> item = slice.dest_items[0]; + EXPECT_EQ(ShareableBlobDataItem::POPULATED_WITHOUT_QUOTA, item->state()); + + const DataElement& dest_element = item->item()->data_element(); + EXPECT_EQ(DataElement::TYPE_FILE, dest_element.type()); + EXPECT_EQ(static_cast<uint64_t>(5), dest_element.length()); + EXPECT_EQ(*item1, *slice.first_source_item); + ASSERT_EQ(1u, slice.dest_items.size()); +} + +} // namespace storage diff --git a/chromium/storage/browser/blob/blob_storage_context_unittest.cc b/chromium/storage/browser/blob/blob_storage_context_unittest.cc new file mode 100644 index 00000000000..5b2abebb209 --- /dev/null +++ b/chromium/storage/browser/blob/blob_storage_context_unittest.cc @@ -0,0 +1,896 @@ +// Copyright 2014 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 "storage/browser/blob/blob_storage_context.h" + +#include <stdint.h> + +#include <limits> +#include <memory> +#include <string> + +#include "base/bind.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/test/test_simple_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "net/base/io_buffer.h" +#include "net/base/test_completion_callback.h" +#include "net/disk_cache/disk_cache.h" +#include "storage/browser/blob/blob_data_builder.h" +#include "storage/browser/blob/blob_data_handle.h" +#include "storage/browser/blob/blob_data_item.h" +#include "storage/browser/blob/blob_data_snapshot.h" +#include "storage/browser/blob/blob_transport_host.h" +#include "storage/common/blob_storage/blob_item_bytes_request.h" +#include "storage/common/blob_storage/blob_item_bytes_response.h" +#include "testing/gtest/include/gtest/gtest.h" + +using RequestMemoryCallback = storage::BlobTransportHost::RequestMemoryCallback; +using FileCreationInfo = storage::BlobMemoryController::FileCreationInfo; + +namespace storage { +namespace { +using base::TestSimpleTaskRunner; + +const int kTestDiskCacheStreamIndex = 0; + +const std::string kBlobStorageDirectory = "blob_storage"; +const size_t kTestBlobStorageIPCThresholdBytes = 20; +const size_t kTestBlobStorageMaxSharedMemoryBytes = 50; + +const size_t kTestBlobStorageMaxBlobMemorySize = 400; +const uint64_t kTestBlobStorageMaxDiskSpace = 4000; +const uint64_t kTestBlobStorageMinFileSizeBytes = 10; +const uint64_t kTestBlobStorageMaxFileSizeBytes = 100; + +// Our disk cache tests don't need a real data handle since the tests themselves +// scope the disk cache and entries. +class EmptyDataHandle : public storage::BlobDataBuilder::DataHandle { + private: + ~EmptyDataHandle() override {} +}; + +std::unique_ptr<disk_cache::Backend> CreateInMemoryDiskCache() { + std::unique_ptr<disk_cache::Backend> cache; + net::TestCompletionCallback callback; + int rv = disk_cache::CreateCacheBackend(net::MEMORY_CACHE, + net::CACHE_BACKEND_DEFAULT, + base::FilePath(), 0, + false, nullptr, nullptr, &cache, + callback.callback()); + EXPECT_EQ(net::OK, callback.GetResult(rv)); + + return cache; +} + +disk_cache::ScopedEntryPtr CreateDiskCacheEntry(disk_cache::Backend* cache, + const char* key, + const std::string& data) { + disk_cache::Entry* temp_entry = nullptr; + net::TestCompletionCallback callback; + int rv = cache->CreateEntry(key, &temp_entry, callback.callback()); + if (callback.GetResult(rv) != net::OK) + return nullptr; + disk_cache::ScopedEntryPtr entry(temp_entry); + + scoped_refptr<net::StringIOBuffer> iobuffer = new net::StringIOBuffer(data); + rv = entry->WriteData(kTestDiskCacheStreamIndex, 0, iobuffer.get(), + iobuffer->size(), callback.callback(), false); + EXPECT_EQ(static_cast<int>(data.size()), callback.GetResult(rv)); + return entry; +} + +void SaveBlobStatus(BlobStatus* status_ptr, BlobStatus status) { + *status_ptr = status; +} + +void SaveBlobStatusAndFiles(BlobStatus* status_ptr, + std::vector<FileCreationInfo>* files_ptr, + BlobStatus status, + std::vector<FileCreationInfo> files) { + EXPECT_FALSE(BlobStatusIsError(status)); + *status_ptr = status; + for (FileCreationInfo& info : files) { + files_ptr->push_back(std::move(info)); + } +} + +void IncrementNumber(size_t* number, BlobStatus status) { + EXPECT_EQ(BlobStatus::DONE, status); + *number = *number + 1; +} + +} // namespace + +class BlobStorageContextTest : public testing::Test { + protected: + BlobStorageContextTest() {} + ~BlobStorageContextTest() override {} + + void SetUp() override { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + context_ = base::MakeUnique<BlobStorageContext>(); + } + + void TearDown() override { + base::RunLoop().RunUntilIdle(); + file_runner_->RunPendingTasks(); + ASSERT_TRUE(temp_dir_.Delete()); + } + + std::unique_ptr<BlobDataHandle> SetupBasicBlob(const std::string& id) { + BlobDataBuilder builder(id); + builder.AppendData("1", 1); + builder.set_content_type("text/plain"); + return context_->AddFinishedBlob(builder); + } + + void SetTestMemoryLimits() { + BlobStorageLimits limits; + limits.max_ipc_memory_size = kTestBlobStorageIPCThresholdBytes; + limits.max_shared_memory_size = kTestBlobStorageMaxSharedMemoryBytes; + limits.max_blob_in_memory_space = kTestBlobStorageMaxBlobMemorySize; + limits.desired_max_disk_space = kTestBlobStorageMaxDiskSpace; + limits.effective_max_disk_space = kTestBlobStorageMaxDiskSpace; + limits.min_page_file_size = kTestBlobStorageMinFileSizeBytes; + limits.max_file_size = kTestBlobStorageMaxFileSizeBytes; + context_->mutable_memory_controller()->set_limits_for_testing(limits); + } + + void IncrementRefCount(const std::string& uuid) { + context_->IncrementBlobRefCount(uuid); + } + + void DecrementRefCount(const std::string& uuid) { + context_->DecrementBlobRefCount(uuid); + } + + std::vector<FileCreationInfo> files_; + base::ScopedTempDir temp_dir_; + scoped_refptr<TestSimpleTaskRunner> file_runner_ = new TestSimpleTaskRunner(); + + base::MessageLoop fake_io_message_loop_; + std::unique_ptr<BlobStorageContext> context_; +}; + +TEST_F(BlobStorageContextTest, BuildBlobAsync) { + const std::string kId("id"); + const size_t kSize = 10u; + BlobStatus status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; + + BlobDataBuilder builder(kId); + builder.AppendFutureData(kSize); + builder.set_content_type("text/plain"); + EXPECT_EQ(0lu, context_->memory_controller().memory_usage()); + std::unique_ptr<BlobDataHandle> handle = context_->BuildBlob( + builder, base::Bind(&SaveBlobStatusAndFiles, &status, &files_)); + EXPECT_EQ(10lu, context_->memory_controller().memory_usage()); + EXPECT_TRUE(handle->IsBeingBuilt()) + << static_cast<int>(handle->GetBlobStatus()); + EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, status); + + BlobStatus construction_done = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; + handle->RunOnConstructionComplete( + base::Bind(&SaveBlobStatus, &construction_done)); + + EXPECT_EQ(10u, context_->memory_controller().memory_usage()); + + builder.PopulateFutureData(0, "abcdefghij", 0, 10u); + context_->NotifyTransportComplete(kId); + + // Check we're done. + EXPECT_EQ(BlobStatus::DONE, handle->GetBlobStatus()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(BlobStatus::DONE, construction_done); + + EXPECT_EQ(builder, *handle->CreateSnapshot()); + + handle.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0lu, context_->memory_controller().memory_usage()); +} + +TEST_F(BlobStorageContextTest, BuildBlobAndCancel) { + const std::string kId("id"); + const size_t kSize = 10u; + BlobStatus status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; + + BlobDataBuilder builder(kId); + builder.AppendFutureData(kSize); + builder.set_content_type("text/plain"); + EXPECT_EQ(0lu, context_->memory_controller().memory_usage()); + std::unique_ptr<BlobDataHandle> handle = context_->BuildBlob( + builder, base::Bind(&SaveBlobStatusAndFiles, &status, &files_)); + EXPECT_EQ(10lu, context_->memory_controller().memory_usage()); + EXPECT_TRUE(handle->IsBeingBuilt()); + EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, status); + EXPECT_EQ(10u, context_->memory_controller().memory_usage()); + + BlobStatus construction_done = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; + handle->RunOnConstructionComplete( + base::Bind(&SaveBlobStatus, &construction_done)); + + context_->CancelBuildingBlob(kId, BlobStatus::ERR_SOURCE_DIED_IN_TRANSIT); + EXPECT_TRUE(handle->IsBroken()); + EXPECT_EQ(0lu, context_->memory_controller().memory_usage()); + + // Check we're broken. + EXPECT_EQ(BlobStatus::ERR_SOURCE_DIED_IN_TRANSIT, handle->GetBlobStatus()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(BlobStatus::ERR_SOURCE_DIED_IN_TRANSIT, construction_done); +} + +TEST_F(BlobStorageContextTest, CancelledReference) { + const std::string kId1("id1"); + const std::string kId2("id2"); + const size_t kSize = 10u; + BlobStatus status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; + + // Start our first blob. + BlobDataBuilder builder(kId1); + builder.AppendFutureData(kSize); + builder.set_content_type("text/plain"); + EXPECT_EQ(0lu, context_->memory_controller().memory_usage()); + std::unique_ptr<BlobDataHandle> handle = context_->BuildBlob( + builder, base::Bind(&SaveBlobStatusAndFiles, &status, &files_)); + EXPECT_EQ(10lu, context_->memory_controller().memory_usage()); + EXPECT_TRUE(handle->IsBeingBuilt()); + EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, status); + + BlobStatus construction_done = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; + handle->RunOnConstructionComplete( + base::Bind(&SaveBlobStatus, &construction_done)); + + EXPECT_EQ(10u, context_->memory_controller().memory_usage()); + + // Create our second blob, which depends on the first. + BlobDataBuilder builder2(kId2); + builder2.AppendBlob(kId1); + builder2.set_content_type("text/plain"); + std::unique_ptr<BlobDataHandle> handle2 = context_->BuildBlob( + builder2, BlobStorageContext::TransportAllowedCallback()); + BlobStatus construction_done2 = + BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; + handle->RunOnConstructionComplete( + base::Bind(&SaveBlobStatus, &construction_done2)); + EXPECT_TRUE(handle2->IsBeingBuilt()); + + EXPECT_EQ(10lu, context_->memory_controller().memory_usage()); + + // Cancel the first blob. + context_->CancelBuildingBlob(kId1, BlobStatus::ERR_SOURCE_DIED_IN_TRANSIT); + + base::RunLoop().RunUntilIdle(); + // Check we broke successfully. + EXPECT_EQ(BlobStatus::ERR_SOURCE_DIED_IN_TRANSIT, construction_done); + EXPECT_EQ(BlobStatus::ERR_SOURCE_DIED_IN_TRANSIT, handle->GetBlobStatus()); + EXPECT_EQ(0lu, context_->memory_controller().memory_usage()); + EXPECT_TRUE(handle->IsBroken()); + + // Check that it propagated. + EXPECT_TRUE(handle2->IsBroken()); + EXPECT_EQ(BlobStatus::ERR_SOURCE_DIED_IN_TRANSIT, construction_done2); + EXPECT_EQ(BlobStatus::ERR_SOURCE_DIED_IN_TRANSIT, handle->GetBlobStatus()); +} + +TEST_F(BlobStorageContextTest, IncorrectSlice) { + const std::string kId1("id1"); + const std::string kId2("id2"); + + std::unique_ptr<BlobDataHandle> handle = SetupBasicBlob(kId1); + + EXPECT_EQ(1lu, context_->memory_controller().memory_usage()); + + BlobDataBuilder builder(kId2); + builder.AppendBlob(kId1, 1, 10); + std::unique_ptr<BlobDataHandle> handle2 = context_->BuildBlob( + builder, BlobStorageContext::TransportAllowedCallback()); + + EXPECT_TRUE(handle2->IsBroken()); + EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS, + handle2->GetBlobStatus()); +} + +TEST_F(BlobStorageContextTest, IncrementDecrementRef) { + // Build up a basic blob. + const std::string kId("id"); + std::unique_ptr<BlobDataHandle> blob_data_handle = SetupBasicBlob(kId); + + // Do an extra increment to keep it around after we kill the handle. + IncrementRefCount(kId); + IncrementRefCount(kId); + DecrementRefCount(kId); + blob_data_handle = context_->GetBlobDataFromUUID(kId); + EXPECT_TRUE(blob_data_handle); + blob_data_handle.reset(); + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(context_->registry().HasEntry(kId)); + DecrementRefCount(kId); + EXPECT_FALSE(context_->registry().HasEntry(kId)); + + // Make sure it goes away in the end. + blob_data_handle = context_->GetBlobDataFromUUID(kId); + EXPECT_FALSE(blob_data_handle); +} + +TEST_F(BlobStorageContextTest, BlobDataHandle) { + // Build up a basic blob. + const std::string kId("id"); + std::unique_ptr<BlobDataHandle> blob_data_handle = SetupBasicBlob(kId); + EXPECT_TRUE(blob_data_handle); + + // Get another handle + std::unique_ptr<BlobDataHandle> another_handle = + context_->GetBlobDataFromUUID(kId); + EXPECT_TRUE(another_handle); + + // Should disappear after dropping both handles. + blob_data_handle.reset(); + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(context_->registry().HasEntry(kId)); + + another_handle.reset(); + base::RunLoop().RunUntilIdle(); + + blob_data_handle = context_->GetBlobDataFromUUID(kId); + EXPECT_FALSE(blob_data_handle); +} + +TEST_F(BlobStorageContextTest, MemoryUsage) { + const std::string kId1("id1"); + const std::string kId2("id2"); + + BlobDataBuilder builder1(kId1); + BlobDataBuilder builder2(kId2); + builder1.AppendData("Data1Data2"); + builder2.AppendBlob(kId1); + builder2.AppendBlob(kId1); + builder2.AppendBlob(kId1); + builder2.AppendBlob(kId1); + builder2.AppendBlob(kId1); + builder2.AppendBlob(kId1); + builder2.AppendBlob(kId1); + + EXPECT_EQ(0lu, context_->memory_controller().memory_usage()); + + std::unique_ptr<BlobDataHandle> blob_data_handle = + context_->AddFinishedBlob(&builder1); + EXPECT_EQ(10lu, context_->memory_controller().memory_usage()); + std::unique_ptr<BlobDataHandle> blob_data_handle2 = + context_->AddFinishedBlob(&builder2); + EXPECT_EQ(10lu, context_->memory_controller().memory_usage()); + + EXPECT_EQ(2u, context_->registry().blob_count()); + + blob_data_handle.reset(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(10lu, context_->memory_controller().memory_usage()); + EXPECT_EQ(1u, context_->registry().blob_count()); + blob_data_handle2.reset(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(0lu, context_->memory_controller().memory_usage()); + EXPECT_EQ(0u, context_->registry().blob_count()); +} + +TEST_F(BlobStorageContextTest, AddFinishedBlob) { + const std::string kId1("id1"); + const std::string kId2("id12"); + const std::string kId3("id3"); + + BlobDataBuilder builder1(kId1); + BlobDataBuilder builder2(kId2); + BlobDataBuilder canonicalized_blob_data2(kId2); + builder1.AppendData("Data1Data2"); + builder2.AppendBlob(kId1, 5, 5); + builder2.AppendData(" is the best"); + canonicalized_blob_data2.AppendData("Data2"); + canonicalized_blob_data2.AppendData(" is the best"); + + BlobStorageContext context; + + std::unique_ptr<BlobDataHandle> blob_data_handle = + context_->AddFinishedBlob(&builder1); + std::unique_ptr<BlobDataHandle> blob_data_handle2 = + context_->AddFinishedBlob(&builder2); + + EXPECT_EQ(10u + 12u + 5u, context_->memory_controller().memory_usage()); + + ASSERT_TRUE(blob_data_handle); + ASSERT_TRUE(blob_data_handle2); + std::unique_ptr<BlobDataSnapshot> data1 = blob_data_handle->CreateSnapshot(); + std::unique_ptr<BlobDataSnapshot> data2 = blob_data_handle2->CreateSnapshot(); + EXPECT_EQ(*data1, builder1); + EXPECT_EQ(*data2, canonicalized_blob_data2); + blob_data_handle.reset(); + data2.reset(); + + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(12u + 5u, context_->memory_controller().memory_usage()); + + blob_data_handle = context_->GetBlobDataFromUUID(kId1); + EXPECT_FALSE(blob_data_handle); + EXPECT_TRUE(blob_data_handle2); + data2 = blob_data_handle2->CreateSnapshot(); + EXPECT_EQ(*data2, canonicalized_blob_data2); + + // Test shared elements stick around. + BlobDataBuilder builder3(kId3); + builder3.AppendBlob(kId2); + builder3.AppendBlob(kId2); + std::unique_ptr<BlobDataHandle> blob_data_handle3 = + context_->AddFinishedBlob(&builder3); + EXPECT_FALSE(blob_data_handle3->IsBeingBuilt()); + blob_data_handle2.reset(); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(12u + 5u, context_->memory_controller().memory_usage()); + + blob_data_handle2 = context_->GetBlobDataFromUUID(kId2); + EXPECT_FALSE(blob_data_handle2); + EXPECT_TRUE(blob_data_handle3); + std::unique_ptr<BlobDataSnapshot> data3 = blob_data_handle3->CreateSnapshot(); + + BlobDataBuilder canonicalized_blob_data3(kId3); + canonicalized_blob_data3.AppendData("Data2"); + canonicalized_blob_data3.AppendData(" is the best"); + canonicalized_blob_data3.AppendData("Data2"); + canonicalized_blob_data3.AppendData(" is the best"); + EXPECT_EQ(*data3, canonicalized_blob_data3); + + blob_data_handle.reset(); + blob_data_handle2.reset(); + blob_data_handle3.reset(); + base::RunLoop().RunUntilIdle(); +} + +TEST_F(BlobStorageContextTest, AddFinishedBlob_LargeOffset) { + // A value which does not fit in a 4-byte data type. Used to confirm that + // large values are supported on 32-bit Chromium builds. Regression test for: + // crbug.com/458122. + const uint64_t kLargeSize = std::numeric_limits<uint64_t>::max() - 1; + + const uint64_t kBlobLength = 5; + const std::string kId1("id1"); + const std::string kId2("id2"); + + BlobDataBuilder builder1(kId1); + builder1.AppendFileSystemFile(GURL(), 0, kLargeSize, base::Time::Now()); + + BlobDataBuilder builder2(kId2); + builder2.AppendBlob(kId1, kLargeSize - kBlobLength, kBlobLength); + + std::unique_ptr<BlobDataHandle> blob_data_handle1 = + context_->AddFinishedBlob(&builder1); + std::unique_ptr<BlobDataHandle> blob_data_handle2 = + context_->AddFinishedBlob(&builder2); + + ASSERT_TRUE(blob_data_handle1); + ASSERT_TRUE(blob_data_handle2); + std::unique_ptr<BlobDataSnapshot> data = blob_data_handle2->CreateSnapshot(); + ASSERT_EQ(1u, data->items().size()); + const scoped_refptr<BlobDataItem> item = data->items()[0]; + EXPECT_EQ(kLargeSize - kBlobLength, item->offset()); + EXPECT_EQ(kBlobLength, item->length()); + + blob_data_handle1.reset(); + blob_data_handle2.reset(); + base::RunLoop().RunUntilIdle(); +} + +TEST_F(BlobStorageContextTest, BuildDiskCacheBlob) { + scoped_refptr<BlobDataBuilder::DataHandle> + data_handle = new EmptyDataHandle(); + + { + BlobStorageContext context; + + std::unique_ptr<disk_cache::Backend> cache = CreateInMemoryDiskCache(); + ASSERT_TRUE(cache); + + const std::string kTestBlobData = "Test Blob Data"; + disk_cache::ScopedEntryPtr entry = + CreateDiskCacheEntry(cache.get(), "test entry", kTestBlobData); + + const std::string kId1Prime("id1.prime"); + BlobDataBuilder canonicalized_blob_data(kId1Prime); + canonicalized_blob_data.AppendData(kTestBlobData.c_str()); + + const std::string kId1("id1"); + BlobDataBuilder builder(kId1); + + builder.AppendDiskCacheEntry( + data_handle, entry.get(), kTestDiskCacheStreamIndex); + + std::unique_ptr<BlobDataHandle> blob_data_handle = + context.AddFinishedBlob(&builder); + std::unique_ptr<BlobDataSnapshot> data = blob_data_handle->CreateSnapshot(); + EXPECT_EQ(*data, builder); + EXPECT_FALSE(data_handle->HasOneRef()) + << "Data handle was destructed while context and builder still exist."; + } + EXPECT_TRUE(data_handle->HasOneRef()) + << "Data handle was not destructed along with blob storage context."; + base::RunLoop().RunUntilIdle(); +} + +TEST_F(BlobStorageContextTest, BuildFutureFileOnlyBlob) { + const std::string kId1("id1"); + context_ = + base::MakeUnique<BlobStorageContext>(temp_dir_.GetPath(), file_runner_); + SetTestMemoryLimits(); + + BlobDataBuilder builder(kId1); + builder.set_content_type("text/plain"); + builder.AppendFutureFile(0, 10, 0); + + BlobStatus status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; + std::unique_ptr<BlobDataHandle> handle = context_->BuildBlob( + builder, base::Bind(&SaveBlobStatusAndFiles, &status, &files_)); + + size_t blobs_finished = 0; + EXPECT_EQ(BlobStatus::PENDING_QUOTA, handle->GetBlobStatus()); + EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS, status); + handle->RunOnConstructionComplete( + base::Bind(&IncrementNumber, &blobs_finished)); + EXPECT_EQ(0u, blobs_finished); + + EXPECT_TRUE(file_runner_->HasPendingTask()); + file_runner_->RunPendingTasks(); + EXPECT_EQ(0u, blobs_finished); + EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS, status); + EXPECT_EQ(BlobStatus::PENDING_QUOTA, handle->GetBlobStatus()); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, status); + EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, handle->GetBlobStatus()); + EXPECT_EQ(0u, blobs_finished); + + ASSERT_EQ(1u, files_.size()); + + builder.PopulateFutureFile(0, files_[0].file_reference, base::Time::Max()); + context_->NotifyTransportComplete(kId1); + + EXPECT_EQ(BlobStatus::DONE, handle->GetBlobStatus()); + EXPECT_EQ(0u, blobs_finished); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1u, blobs_finished); + + builder.Clear(); + handle.reset(); + files_.clear(); + base::RunLoop().RunUntilIdle(); + // We should have file cleanup tasks. + EXPECT_TRUE(file_runner_->HasPendingTask()); + file_runner_->RunPendingTasks(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0lu, context_->memory_controller().memory_usage()); + EXPECT_EQ(0lu, context_->memory_controller().disk_usage()); +} + +TEST_F(BlobStorageContextTest, CompoundBlobs) { + const std::string kId1("id1"); + const std::string kId2("id2"); + const std::string kId3("id3"); + + // Setup a set of blob data for testing. + base::Time time1, time2; + ASSERT_TRUE(base::Time::FromString("Tue, 15 Nov 1994, 12:45:26 GMT", &time1)); + ASSERT_TRUE(base::Time::FromString("Mon, 14 Nov 1994, 11:30:49 GMT", &time2)); + + BlobDataBuilder blob_data1(kId1); + blob_data1.AppendData("Data1"); + blob_data1.AppendData("Data2"); + blob_data1.AppendFile(base::FilePath(FILE_PATH_LITERAL("File1.txt")), 10, + 1024, time1); + + BlobDataBuilder blob_data2(kId2); + blob_data2.AppendData("Data3"); + blob_data2.AppendBlob(kId1, 8, 100); + blob_data2.AppendFile(base::FilePath(FILE_PATH_LITERAL("File2.txt")), 0, 20, + time2); + + BlobDataBuilder blob_data3(kId3); + blob_data3.AppendData("Data4"); + std::unique_ptr<disk_cache::Backend> cache = CreateInMemoryDiskCache(); + ASSERT_TRUE(cache); + disk_cache::ScopedEntryPtr disk_cache_entry = + CreateDiskCacheEntry(cache.get(), "another key", "Data5"); + blob_data3.AppendDiskCacheEntry(new EmptyDataHandle(), disk_cache_entry.get(), + kTestDiskCacheStreamIndex); + + BlobDataBuilder canonicalized_blob_data2(kId2); + canonicalized_blob_data2.AppendData("Data3"); + canonicalized_blob_data2.AppendData("a2___", 2); + canonicalized_blob_data2.AppendFile( + base::FilePath(FILE_PATH_LITERAL("File1.txt")), 10, 98, time1); + canonicalized_blob_data2.AppendFile( + base::FilePath(FILE_PATH_LITERAL("File2.txt")), 0, 20, time2); + + BlobStorageContext context; + std::unique_ptr<BlobDataHandle> blob_data_handle; + + // Test a blob referring to only data and a file. + blob_data_handle = context_->AddFinishedBlob(&blob_data1); + + ASSERT_TRUE(blob_data_handle); + std::unique_ptr<BlobDataSnapshot> data = blob_data_handle->CreateSnapshot(); + ASSERT_TRUE(blob_data_handle); + EXPECT_EQ(*data, blob_data1); + + // Test a blob composed in part with another blob. + blob_data_handle = context_->AddFinishedBlob(&blob_data2); + data = blob_data_handle->CreateSnapshot(); + ASSERT_TRUE(blob_data_handle); + ASSERT_TRUE(data); + EXPECT_EQ(*data, canonicalized_blob_data2); + + // Test a blob referring to only data and a disk cache entry. + blob_data_handle = context_->AddFinishedBlob(&blob_data3); + data = blob_data_handle->CreateSnapshot(); + ASSERT_TRUE(blob_data_handle); + EXPECT_EQ(*data, blob_data3); + + blob_data_handle.reset(); + base::RunLoop().RunUntilIdle(); +} + +TEST_F(BlobStorageContextTest, PublicBlobUrls) { + // Build up a basic blob. + const std::string kId("id"); + std::unique_ptr<BlobDataHandle> first_handle = SetupBasicBlob(kId); + + // Now register a url for that blob. + GURL kUrl("blob:id"); + context_->RegisterPublicBlobURL(kUrl, kId); + std::unique_ptr<BlobDataHandle> blob_data_handle = + context_->GetBlobDataFromPublicURL(kUrl); + ASSERT_TRUE(blob_data_handle.get()); + EXPECT_EQ(kId, blob_data_handle->uuid()); + std::unique_ptr<BlobDataSnapshot> data = blob_data_handle->CreateSnapshot(); + blob_data_handle.reset(); + first_handle.reset(); + base::RunLoop().RunUntilIdle(); + + // The url registration should keep the blob alive even after + // explicit references are dropped. + blob_data_handle = context_->GetBlobDataFromPublicURL(kUrl); + EXPECT_TRUE(blob_data_handle); + blob_data_handle.reset(); + + base::RunLoop().RunUntilIdle(); + // Finally get rid of the url registration and the blob. + context_->RevokePublicBlobURL(kUrl); + blob_data_handle = context_->GetBlobDataFromPublicURL(kUrl); + EXPECT_FALSE(blob_data_handle.get()); + EXPECT_FALSE(context_->registry().HasEntry(kId)); +} + +TEST_F(BlobStorageContextTest, TestUnknownBrokenAndBuildingBlobReference) { + const std::string kBrokenId("broken_id"); + const std::string kBuildingId("building_id"); + const std::string kReferencingId("referencing_id"); + const std::string kUnknownId("unknown_id"); + + // Create a broken blob. + std::unique_ptr<BlobDataHandle> broken_handle = + context_->AddBrokenBlob(kBrokenId, "", "", BlobStatus::ERR_OUT_OF_MEMORY); + EXPECT_TRUE(broken_handle->GetBlobStatus() == BlobStatus::ERR_OUT_OF_MEMORY); + EXPECT_TRUE(context_->registry().HasEntry(kBrokenId)); + + // Try to create a blob with a reference to an unknown blob. + BlobDataBuilder builder(kReferencingId); + builder.AppendData("data"); + builder.AppendBlob(kUnknownId); + std::unique_ptr<BlobDataHandle> handle = context_->AddFinishedBlob(builder); + EXPECT_TRUE(handle->IsBroken()); + EXPECT_TRUE(context_->registry().HasEntry(kReferencingId)); + handle.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(context_->registry().HasEntry(kReferencingId)); + + // Try to create a blob with a reference to the broken blob. + BlobDataBuilder builder2(kReferencingId); + builder2.AppendData("data"); + builder2.AppendBlob(kBrokenId); + handle = context_->AddFinishedBlob(builder2); + EXPECT_TRUE(handle->IsBroken()); + EXPECT_TRUE(context_->registry().HasEntry(kReferencingId)); + handle.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(context_->registry().HasEntry(kReferencingId)); + + // Try to create a blob with a reference to the building blob. + BlobDataBuilder builder3(kReferencingId); + builder3.AppendData("data"); + builder3.AppendBlob(kBuildingId); + handle = context_->AddFinishedBlob(builder3); + EXPECT_TRUE(handle->IsBroken()); + EXPECT_TRUE(context_->registry().HasEntry(kReferencingId)); + handle.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(context_->registry().HasEntry(kReferencingId)); +} + +namespace { +constexpr size_t kTotalRawBlobs = 200; +constexpr size_t kTotalSlicedBlobs = 100; +constexpr char kTestDiskCacheData[] = "Test Blob Data"; + +// Appends data and data types that depend on the index. This is designed to +// exercise all types of combinations of data, future data, files, future files, +// and disk cache entries. +size_t AppendDataInBuilder(BlobDataBuilder* builder, + size_t index, + disk_cache::Entry* cache_entry) { + size_t size = 0; + // We can't have both future data and future files, so split those up. + if (index % 2 != 0) { + builder->AppendFutureData(5u); + size += 5u; + if (index % 3 == 1) { + builder->AppendData("abcdefghij", 4u); + size += 4u; + } + if (index % 3 == 0) { + builder->AppendFutureData(1u); + size += 1u; + } + } else if (index % 3 == 0) { + builder->AppendFutureFile(0lu, 3lu, 0); + size += 3u; + } + if (index % 5 != 0) { + builder->AppendFile( + base::FilePath::FromUTF8Unsafe(base::SizeTToString(index)), 0ul, 20ul, + base::Time::Max()); + size += 20u; + } + if (index % 3 != 0) { + scoped_refptr<BlobDataBuilder::DataHandle> disk_cache_data_handle = + new EmptyDataHandle(); + builder->AppendDiskCacheEntry(disk_cache_data_handle, cache_entry, + kTestDiskCacheStreamIndex); + size += strlen(kTestDiskCacheData); + } + return size; +} + +bool DoesBuilderHaveFutureData(size_t index) { + return index < kTotalRawBlobs && (index % 2 != 0 || index % 3 == 0); +} + +void PopulateDataInBuilder(BlobDataBuilder* builder, + size_t index, + base::TaskRunner* file_runner) { + if (index % 2 != 0) { + builder->PopulateFutureData(0, "abcde", 0, 5); + if (index % 3 == 0) { + builder->PopulateFutureData(1, "z", 0, 1); + } + } else if (index % 3 == 0) { + scoped_refptr<ShareableFileReference> file_ref = + ShareableFileReference::GetOrCreate( + base::FilePath::FromUTF8Unsafe( + base::SizeTToString(index + kTotalRawBlobs)), + ShareableFileReference::DONT_DELETE_ON_FINAL_RELEASE, file_runner); + builder->PopulateFutureFile(0, file_ref, base::Time::Max()); + } +} +} // namespace + +TEST_F(BlobStorageContextTest, BuildBlobCombinations) { + const std::string kId("id"); + + context_ = + base::MakeUnique<BlobStorageContext>(temp_dir_.GetPath(), file_runner_); + + SetTestMemoryLimits(); + std::unique_ptr<disk_cache::Backend> cache = CreateInMemoryDiskCache(); + ASSERT_TRUE(cache); + disk_cache::ScopedEntryPtr entry = + CreateDiskCacheEntry(cache.get(), "test entry", kTestDiskCacheData); + + // This tests mixed blob content with both synchronous and asynchronous + // construction. Blobs should also be paged to disk during execution. + std::vector<std::unique_ptr<BlobDataBuilder>> builders; + std::vector<size_t> sizes; + for (size_t i = 0; i < kTotalRawBlobs; i++) { + builders.emplace_back(new BlobDataBuilder(base::SizeTToString(i))); + auto& builder = *builders.back(); + size_t size = AppendDataInBuilder(&builder, i, entry.get()); + EXPECT_NE(0u, size); + sizes.push_back(size); + } + + for (size_t i = 0; i < kTotalSlicedBlobs; i++) { + builders.emplace_back( + new BlobDataBuilder(base::SizeTToString(i + kTotalRawBlobs))); + size_t source_size = sizes[i]; + size_t offset = source_size == 1 ? 0 : i % (source_size - 1); + size_t size = (i % (source_size - offset)) + 1; + builders.back()->AppendBlob(base::SizeTToString(i), offset, size); + } + + size_t total_finished_blobs = 0; + std::vector<std::unique_ptr<BlobDataHandle>> handles; + std::vector<BlobStatus> statuses; + std::vector<bool> populated; + statuses.resize(kTotalRawBlobs, + BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS); + populated.resize(kTotalRawBlobs, false); + for (size_t i = 0; i < builders.size(); i++) { + BlobDataBuilder& builder = *builders[i]; + builder.set_content_type("text/plain"); + bool has_pending_memory = DoesBuilderHaveFutureData(i); + std::unique_ptr<BlobDataHandle> handle = context_->BuildBlob( + builder, + has_pending_memory + ? base::Bind(&SaveBlobStatusAndFiles, &statuses[0] + i, &files_) + : BlobStorageContext::TransportAllowedCallback()); + handle->RunOnConstructionComplete( + base::Bind(&IncrementNumber, &total_finished_blobs)); + handles.push_back(std::move(handle)); + } + base::RunLoop().RunUntilIdle(); + + // We should be needing to send a page or two to disk. + EXPECT_TRUE(file_runner_->HasPendingTask()); + do { + file_runner_->RunPendingTasks(); + base::RunLoop().RunUntilIdle(); + // Continue populating data for items that can fit. + for (size_t i = 0; i < kTotalRawBlobs; i++) { + BlobDataBuilder* builder = builders[i].get(); + if (DoesBuilderHaveFutureData(i) && !populated[i] && + statuses[i] == BlobStatus::PENDING_TRANSPORT) { + PopulateDataInBuilder(builder, i, file_runner_.get()); + context_->NotifyTransportComplete(base::SizeTToString(i)); + populated[i] = true; + } + } + base::RunLoop().RunUntilIdle(); + } while (file_runner_->HasPendingTask()); + + // Check all builders with future items were signalled and populated. + for (size_t i = 0; i < populated.size(); i++) { + if (DoesBuilderHaveFutureData(i)) { + EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, statuses[i]) << i; + EXPECT_TRUE(populated[i]) << i; + } + } + base::RunLoop().RunUntilIdle(); + + // We should be completely built now. + EXPECT_EQ(kTotalRawBlobs + kTotalSlicedBlobs, total_finished_blobs); + for (std::unique_ptr<BlobDataHandle>& handle : handles) { + EXPECT_EQ(BlobStatus::DONE, handle->GetBlobStatus()); + } + handles.clear(); + base::RunLoop().RunUntilIdle(); + files_.clear(); + // We should have file cleanup tasks. + EXPECT_TRUE(file_runner_->HasPendingTask()); + file_runner_->RunPendingTasks(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0lu, context_->memory_controller().memory_usage()); + EXPECT_EQ(0lu, context_->memory_controller().disk_usage()); +} + +// TODO(michaeln): tests for the deprecated url stuff + +} // namespace storage diff --git a/chromium/storage/browser/blob/blob_storage_registry_unittest.cc b/chromium/storage/browser/blob/blob_storage_registry_unittest.cc new file mode 100644 index 00000000000..9cb2285f11f --- /dev/null +++ b/chromium/storage/browser/blob/blob_storage_registry_unittest.cc @@ -0,0 +1,81 @@ +// Copyright 2015 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 "storage/browser/blob/blob_storage_registry.h" + +#include "base/callback.h" +#include "storage/browser/blob/blob_entry.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace storage { +namespace { + +TEST(BlobStorageRegistry, UUIDRegistration) { + const std::string kBlob1 = "Blob1"; + const std::string kType = "type1"; + const std::string kDisposition = "disp1"; + BlobStorageRegistry registry; + + EXPECT_FALSE(registry.DeleteEntry(kBlob1)); + EXPECT_EQ(0u, registry.blob_count()); + + BlobEntry* entry = registry.CreateEntry(kBlob1, kType, kDisposition); + ASSERT_NE(nullptr, entry); + EXPECT_EQ(BlobStatus::PENDING_QUOTA, entry->status()); + EXPECT_EQ(kType, entry->content_type()); + EXPECT_EQ(kDisposition, entry->content_disposition()); + EXPECT_EQ(0u, entry->refcount()); + + EXPECT_EQ(entry, registry.GetEntry(kBlob1)); + EXPECT_TRUE(registry.DeleteEntry(kBlob1)); + entry = registry.CreateEntry(kBlob1, kType, kDisposition); + + EXPECT_EQ(1u, registry.blob_count()); +} + +TEST(BlobStorageRegistry, URLRegistration) { + const std::string kBlob = "Blob1"; + const std::string kType = "type1"; + const std::string kDisposition = "disp1"; + const std::string kBlob2 = "Blob2"; + const GURL kURL = GURL("blob://Blob1"); + const GURL kURL2 = GURL("blob://Blob2"); + BlobStorageRegistry registry; + + EXPECT_FALSE(registry.IsURLMapped(kURL)); + EXPECT_EQ(nullptr, registry.GetEntryFromURL(kURL, nullptr)); + EXPECT_FALSE(registry.DeleteURLMapping(kURL, nullptr)); + EXPECT_FALSE(registry.CreateUrlMapping(kURL, kBlob)); + EXPECT_EQ(0u, registry.url_count()); + BlobEntry* entry = registry.CreateEntry(kBlob, kType, kDisposition); + + EXPECT_FALSE(registry.IsURLMapped(kURL)); + EXPECT_TRUE(registry.CreateUrlMapping(kURL, kBlob)); + EXPECT_FALSE(registry.CreateUrlMapping(kURL, kBlob2)); + + EXPECT_TRUE(registry.IsURLMapped(kURL)); + EXPECT_EQ(entry, registry.GetEntryFromURL(kURL, nullptr)); + std::string uuid; + EXPECT_EQ(entry, registry.GetEntryFromURL(kURL, &uuid)); + EXPECT_EQ(kBlob, uuid); + EXPECT_EQ(1u, registry.url_count()); + + registry.CreateEntry(kBlob2, kType, kDisposition); + EXPECT_TRUE(registry.CreateUrlMapping(kURL2, kBlob2)); + EXPECT_EQ(2u, registry.url_count()); + EXPECT_TRUE(registry.DeleteURLMapping(kURL2, &uuid)); + EXPECT_EQ(kBlob2, uuid); + EXPECT_FALSE(registry.IsURLMapped(kURL2)); + + // Both urls point to the same blob. + EXPECT_TRUE(registry.CreateUrlMapping(kURL2, kBlob)); + std::string uuid2; + EXPECT_EQ(registry.GetEntryFromURL(kURL, &uuid), + registry.GetEntryFromURL(kURL2, &uuid2)); + EXPECT_EQ(uuid, uuid2); +} + +} // namespace +} // namespace storage diff --git a/chromium/storage/browser/blob/blob_transport_request_builder_unittest.cc b/chromium/storage/browser/blob/blob_transport_request_builder_unittest.cc new file mode 100644 index 00000000000..71e0956113f --- /dev/null +++ b/chromium/storage/browser/blob/blob_transport_request_builder_unittest.cc @@ -0,0 +1,350 @@ +// Copyright 2015 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 <stddef.h> +#include <stdint.h> + +#include <string> + +#include "base/logging.h" +#include "storage/browser/blob/blob_transport_request_builder.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace storage { +namespace { + +const char kNewUUID[] = "newUUID"; +const char kFakeBlobUUID[] = "fakeBlob"; + +void AddMemoryItem(size_t length, std::vector<DataElement>* out) { + DataElement bytes; + bytes.SetToBytesDescription(length); + out->push_back(bytes); +} + +void AddShortcutMemoryItem(size_t length, std::vector<DataElement>* out) { + DataElement bytes; + bytes.SetToAllocatedBytes(length); + for (size_t i = 0; i < length; i++) { + bytes.mutable_bytes()[i] = static_cast<char>(i); + } + out->push_back(bytes); +} + +void AddBlobItem(std::vector<DataElement>* out) { + DataElement blob; + blob.SetToBlob(kFakeBlobUUID); + out->push_back(blob); +} + +TEST(BlobAsyncTransportRequestBuilderTest, TestNoMemoryItems) { + BlobTransportRequestBuilder strategy; + BlobDataBuilder builder(kNewUUID); + std::vector<DataElement> infos; + + // Here we test that we don't do any requests when there are no memory items. + AddBlobItem(&infos); + AddBlobItem(&infos); + AddBlobItem(&infos); + strategy.InitializeForIPCRequests(100, // max_ipc_memory_size + 0, // blob_total_size + infos, &builder); + + EXPECT_EQ(0u, strategy.shared_memory_sizes().size()); + EXPECT_EQ(0u, strategy.file_sizes().size()); + EXPECT_EQ(0u, strategy.requests().size()); + + BlobDataBuilder expected_builder(kNewUUID); + expected_builder.AppendBlob(kFakeBlobUUID); + expected_builder.AppendBlob(kFakeBlobUUID); + expected_builder.AppendBlob(kFakeBlobUUID); + EXPECT_EQ(expected_builder, builder); +} + +TEST(BlobAsyncTransportRequestBuilderTest, TestLargeBlockToFile) { + BlobTransportRequestBuilder strategy; + BlobDataBuilder builder(kNewUUID); + std::vector<DataElement> infos; + + AddMemoryItem(305, &infos); + strategy.InitializeForFileRequests(400, // max_file_size + 305, // blob_total_size + infos, &builder); + + EXPECT_EQ(0u, strategy.shared_memory_sizes().size()); + EXPECT_EQ(1u, strategy.file_sizes().size()); + EXPECT_EQ(305ul, strategy.file_sizes().at(0)); + EXPECT_EQ(1u, strategy.requests().size()); + + auto& memory_item_request = strategy.requests().at(0); + EXPECT_EQ(0u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ( + BlobItemBytesRequest::CreateFileRequest(0u, 0u, 0ull, 305ull, 0u, 0ull), + memory_item_request.message); + + BlobDataBuilder expected_builder(kNewUUID); + expected_builder.AppendFutureFile(0, 305, 0); + EXPECT_EQ(expected_builder, builder); +} + +TEST(BlobAsyncTransportRequestBuilderTest, TestLargeBlockToFiles) { + BlobTransportRequestBuilder strategy; + BlobDataBuilder builder(kNewUUID); + std::vector<DataElement> infos; + + AddMemoryItem(1000, &infos); + strategy.InitializeForFileRequests(400, // max_file_size + 1000, // blob_total_size + infos, &builder); + + EXPECT_EQ(0u, strategy.shared_memory_sizes().size()); + EXPECT_EQ(3u, strategy.file_sizes().size()); + EXPECT_EQ(400ul, strategy.file_sizes().at(0)); + EXPECT_EQ(400ul, strategy.file_sizes().at(1)); + EXPECT_EQ(200ul, strategy.file_sizes().at(2)); + EXPECT_EQ(3u, strategy.requests().size()); + + auto memory_item_request = strategy.requests().at(0); + EXPECT_EQ(0u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ( + BlobItemBytesRequest::CreateFileRequest(0u, 0u, 0ull, 400ull, 0u, 0ull), + memory_item_request.message); + + memory_item_request = strategy.requests().at(1); + EXPECT_EQ(1u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ( + BlobItemBytesRequest::CreateFileRequest(1u, 0u, 400ull, 400ull, 1u, 0ull), + memory_item_request.message); + + memory_item_request = strategy.requests().at(2); + EXPECT_EQ(2u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ( + BlobItemBytesRequest::CreateFileRequest(2u, 0u, 800ull, 200ull, 2u, 0ull), + memory_item_request.message); + + BlobDataBuilder expected_builder(kNewUUID); + expected_builder.AppendFutureFile(0, 400, 0); + expected_builder.AppendFutureFile(0, 400, 1); + expected_builder.AppendFutureFile(0, 200, 2); + EXPECT_EQ(expected_builder, builder); +} + +TEST(BlobAsyncTransportRequestBuilderTest, + TestLargeBlocksConsolidatingInFiles) { + BlobTransportRequestBuilder strategy; + BlobDataBuilder builder(kNewUUID); + std::vector<DataElement> infos; + + // We should have 3 storage items for the memory, two files, 400 each. + // We end up with storage items: + // 1: File A, 300MB + // 2: Blob + // 3: File A, 100MB (300MB offset) + // 4: File B, 400MB + AddMemoryItem(300, &infos); + AddBlobItem(&infos); + AddMemoryItem(500, &infos); + + strategy.InitializeForFileRequests(400, // max_file_size + 800, // blob_total_size + infos, &builder); + + EXPECT_EQ(0u, strategy.shared_memory_sizes().size()); + EXPECT_EQ(2u, strategy.file_sizes().size()); + EXPECT_EQ(400ul, strategy.file_sizes().at(0)); + EXPECT_EQ(400ul, strategy.file_sizes().at(1)); + EXPECT_EQ(3u, strategy.requests().size()); + + auto memory_item_request = strategy.requests().at(0); + EXPECT_EQ(0u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ( + BlobItemBytesRequest::CreateFileRequest(0u, 0u, 0ull, 300ull, 0u, 0ull), + memory_item_request.message); + + memory_item_request = strategy.requests().at(1); + EXPECT_EQ(2u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ( + BlobItemBytesRequest::CreateFileRequest(1u, 2u, 0ull, 100ull, 0u, 300ull), + memory_item_request.message); + + memory_item_request = strategy.requests().at(2); + EXPECT_EQ(3u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ( + BlobItemBytesRequest::CreateFileRequest(2u, 2u, 100ull, 400ull, 1u, 0ull), + memory_item_request.message); +} + +TEST(BlobAsyncTransportRequestBuilderTest, TestSharedMemorySegmentation) { + BlobTransportRequestBuilder strategy; + BlobDataBuilder builder(kNewUUID); + std::vector<DataElement> infos; + + // For transport we should have 3 shared memories, and then storage in 3 + // browser items. + AddMemoryItem(500, &infos); + strategy.InitializeForSharedMemoryRequests(200, // max_shared_memory_size + 500, // total_blob_size + infos, &builder); + + EXPECT_EQ(0u, strategy.file_sizes().size()); + EXPECT_EQ(3u, strategy.shared_memory_sizes().size()); + EXPECT_EQ(200u, strategy.shared_memory_sizes().at(0)); + EXPECT_EQ(200u, strategy.shared_memory_sizes().at(1)); + EXPECT_EQ(100u, strategy.shared_memory_sizes().at(2)); + EXPECT_EQ(3u, strategy.requests().size()); + + auto memory_item_request = strategy.requests().at(0); + EXPECT_EQ(0u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ(BlobItemBytesRequest::CreateSharedMemoryRequest(0u, 0u, 0ull, + 200ull, 0u, 0ull), + memory_item_request.message); + + memory_item_request = strategy.requests().at(1); + EXPECT_EQ(1u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ(BlobItemBytesRequest::CreateSharedMemoryRequest(1u, 0u, 200ull, + 200ull, 1u, 0ull), + memory_item_request.message); + + memory_item_request = strategy.requests().at(2); + EXPECT_EQ(2u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ(BlobItemBytesRequest::CreateSharedMemoryRequest(2u, 0u, 400ull, + 100ull, 2u, 0ull), + memory_item_request.message); + + BlobDataBuilder expected_builder(kNewUUID); + expected_builder.AppendFutureData(200); + expected_builder.AppendFutureData(200); + expected_builder.AppendFutureData(100); + EXPECT_EQ(expected_builder, builder); +} + +TEST(BlobAsyncTransportRequestBuilderTest, + TestSharedMemorySegmentationAndStorage) { + BlobTransportRequestBuilder strategy; + BlobDataBuilder builder(kNewUUID); + std::vector<DataElement> infos; + + // For transport, we should have 2 shared memories, where the first one + // have half 0 and half 3, and then the last one has half 3. + // + // For storage, we should have 3 browser items that match the pre-transport + // version: + // 1: Bytes 100MB + // 2: Blob + // 3: Bytes 200MB + AddShortcutMemoryItem(100, &infos); // should have no behavior change + AddBlobItem(&infos); + AddMemoryItem(200, &infos); + + strategy.InitializeForSharedMemoryRequests(200, // max_shared_memory_size + 300, // total_blob_size + infos, &builder); + + EXPECT_EQ(0u, strategy.file_sizes().size()); + EXPECT_EQ(2u, strategy.shared_memory_sizes().size()); + EXPECT_EQ(200u, strategy.shared_memory_sizes().at(0)); + EXPECT_EQ(100u, strategy.shared_memory_sizes().at(1)); + EXPECT_EQ(3u, strategy.requests().size()); + + auto memory_item_request = strategy.requests().at(0); + EXPECT_EQ(0u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ(BlobItemBytesRequest::CreateSharedMemoryRequest(0u, 0u, 0ull, + 100ull, 0u, 0ull), + memory_item_request.message); + + memory_item_request = strategy.requests().at(1); + EXPECT_EQ(2u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ(BlobItemBytesRequest::CreateSharedMemoryRequest(1u, 2u, 0ull, + 100ull, 0u, 100ull), + memory_item_request.message); + + memory_item_request = strategy.requests().at(2); + EXPECT_EQ(2u, memory_item_request.browser_item_index); + EXPECT_EQ(100u, memory_item_request.browser_item_offset); + EXPECT_EQ(BlobItemBytesRequest::CreateSharedMemoryRequest(2u, 2u, 100ull, + 100ull, 1u, 0ull), + memory_item_request.message); + + BlobDataBuilder expected_builder(kNewUUID); + expected_builder.AppendFutureData(100); + expected_builder.AppendBlob(kFakeBlobUUID); + expected_builder.AppendFutureData(200); + EXPECT_EQ(expected_builder, builder); +} + +TEST(BlobAsyncTransportRequestBuilderTest, TestSimpleIPC) { + // Test simple IPC strategy, where size < max_ipc_memory_size and we have + // just one item. + std::vector<DataElement> infos; + BlobTransportRequestBuilder strategy; + BlobDataBuilder builder(kNewUUID); + AddMemoryItem(10, &infos); + AddBlobItem(&infos); + + strategy.InitializeForIPCRequests(100, // max_ipc_memory_size + 10, // total_blob_size + infos, &builder); + + EXPECT_EQ(0u, strategy.file_sizes().size()); + EXPECT_EQ(0u, strategy.shared_memory_sizes().size()); + ASSERT_EQ(1u, strategy.requests().size()); + + auto memory_item_request = strategy.requests().at(0); + EXPECT_EQ(0u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ(BlobItemBytesRequest::CreateIPCRequest(0u, 0u, 0ull, 10ull), + memory_item_request.message); +} + +TEST(BlobAsyncTransportRequestBuilderTest, TestMultipleIPC) { + // Same as above, but with 2 items and a blob in-between. + std::vector<DataElement> infos; + BlobTransportRequestBuilder strategy; + BlobDataBuilder builder(kNewUUID); + AddShortcutMemoryItem(10, &infos); // should have no behavior change + AddBlobItem(&infos); + AddMemoryItem(80, &infos); + + strategy.InitializeForIPCRequests(100, // max_ipc_memory_size + 90, // total_blob_size + infos, &builder); + + EXPECT_EQ(0u, strategy.file_sizes().size()); + EXPECT_EQ(0u, strategy.shared_memory_sizes().size()); + ASSERT_EQ(2u, strategy.requests().size()); + + auto memory_item_request = strategy.requests().at(0); + EXPECT_EQ(0u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ(BlobItemBytesRequest::CreateIPCRequest(0u, 0u, 0ull, 10ull), + memory_item_request.message); + + memory_item_request = strategy.requests().at(1); + EXPECT_EQ(2u, memory_item_request.browser_item_index); + EXPECT_EQ(0u, memory_item_request.browser_item_offset); + EXPECT_EQ(BlobItemBytesRequest::CreateIPCRequest(1u, 2u, 0ull, 80ull), + memory_item_request.message); + + // We still populate future data, as the strategy assumes we will be + // requesting the data. + BlobDataBuilder expected_builder(kNewUUID); + expected_builder.AppendFutureData(10); + expected_builder.AppendBlob(kFakeBlobUUID); + expected_builder.AppendFutureData(80); + EXPECT_EQ(expected_builder, builder); +} +} // namespace +} // namespace storage diff --git a/chromium/storage/browser/blob/blob_url_request_job_unittest.cc b/chromium/storage/browser/blob/blob_url_request_job_unittest.cc new file mode 100644 index 00000000000..10c31138c55 --- /dev/null +++ b/chromium/storage/browser/blob/blob_url_request_job_unittest.cc @@ -0,0 +1,608 @@ +// 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 <stdint.h> + +#include <limits> +#include <memory> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/numerics/safe_conversions.h" +#include "base/run_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "net/base/net_errors.h" +#include "net/base/request_priority.h" +#include "net/base/test_completion_callback.h" +#include "net/disk_cache/disk_cache.h" +#include "net/http/http_byte_range.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_job_factory_impl.h" +#include "net/url_request/url_request_test_util.h" +#include "storage/browser/blob/blob_data_builder.h" +#include "storage/browser/blob/blob_data_handle.h" +#include "storage/browser/blob/blob_data_snapshot.h" +#include "storage/browser/blob/blob_storage_context.h" +#include "storage/browser/blob/blob_url_request_job.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "storage/browser/fileapi/file_system_operation_context.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/test/async_file_test_helper.h" +#include "storage/browser/test/test_file_system_context.h" +#include "testing/gtest/include/gtest/gtest.h" + +using storage::BlobDataSnapshot; +using storage::BlobDataBuilder; +using storage::BlobURLRequestJob; + +namespace content { + +namespace { + +const int kBufferSize = 1024; +const char kTestData1[] = "Hello"; +const char kTestData2[] = "Here it is data."; +const char kTestFileData1[] = "0123456789"; +const char kTestFileData2[] = "This is sample file."; +const char kTestFileSystemFileData1[] = "abcdefghijklmnop"; +const char kTestFileSystemFileData2[] = "File system file test data."; +const char kTestDiskCacheKey1[] = "key1"; +const char kTestDiskCacheKey2[] = "key2"; +const char kTestDiskCacheData1[] = "disk cache test data1."; +const char kTestDiskCacheData2[] = "disk cache test data2."; +const char kTestDiskCacheSideData[] = "test side data"; +const char kTestContentType[] = "foo/bar"; +const char kTestContentDisposition[] = "attachment; filename=foo.txt"; + +const char kFileSystemURLOrigin[] = "http://remote"; +const storage::FileSystemType kFileSystemType = + storage::kFileSystemTypeTemporary; + +const int kTestDiskCacheStreamIndex = 0; +const int kTestDiskCacheSideStreamIndex = 1; + +// Our disk cache tests don't need a real data handle since the tests themselves +// scope the disk cache and entries. +class EmptyDataHandle : public storage::BlobDataBuilder::DataHandle { + private: + ~EmptyDataHandle() override {} +}; + +std::unique_ptr<disk_cache::Backend> CreateInMemoryDiskCache() { + std::unique_ptr<disk_cache::Backend> cache; + net::TestCompletionCallback callback; + int rv = disk_cache::CreateCacheBackend( + net::MEMORY_CACHE, net::CACHE_BACKEND_DEFAULT, base::FilePath(), 0, false, + nullptr, nullptr, &cache, callback.callback()); + EXPECT_EQ(net::OK, callback.GetResult(rv)); + + return cache; +} + +disk_cache::ScopedEntryPtr CreateDiskCacheEntry(disk_cache::Backend* cache, + const char* key, + const std::string& data) { + disk_cache::Entry* temp_entry = nullptr; + net::TestCompletionCallback callback; + int rv = cache->CreateEntry(key, &temp_entry, callback.callback()); + if (callback.GetResult(rv) != net::OK) + return nullptr; + disk_cache::ScopedEntryPtr entry(temp_entry); + + scoped_refptr<net::StringIOBuffer> iobuffer = new net::StringIOBuffer(data); + rv = entry->WriteData(kTestDiskCacheStreamIndex, 0, iobuffer.get(), + iobuffer->size(), callback.callback(), false); + EXPECT_EQ(static_cast<int>(data.size()), callback.GetResult(rv)); + return entry; +} + +disk_cache::ScopedEntryPtr CreateDiskCacheEntryWithSideData( + disk_cache::Backend* cache, + const char* key, + const std::string& data, + const std::string& side_data) { + disk_cache::ScopedEntryPtr entry = CreateDiskCacheEntry(cache, key, data); + scoped_refptr<net::StringIOBuffer> iobuffer = + new net::StringIOBuffer(side_data); + net::TestCompletionCallback callback; + int rv = entry->WriteData(kTestDiskCacheSideStreamIndex, 0, iobuffer.get(), + iobuffer->size(), callback.callback(), false); + EXPECT_EQ(static_cast<int>(side_data.size()), callback.GetResult(rv)); + return entry; +} + +} // namespace + +class BlobURLRequestJobTest : public testing::Test { + public: + // A simple ProtocolHandler implementation to create BlobURLRequestJob. + class MockProtocolHandler + : public net::URLRequestJobFactory::ProtocolHandler { + public: + MockProtocolHandler(BlobURLRequestJobTest* test) : test_(test) {} + + // net::URLRequestJobFactory::ProtocolHandler override. + net::URLRequestJob* MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const override { + return new BlobURLRequestJob(request, network_delegate, + test_->GetHandleFromBuilder(), + test_->file_system_context_.get(), + base::ThreadTaskRunnerHandle::Get().get()); + } + + private: + BlobURLRequestJobTest* test_; + }; + + BlobURLRequestJobTest() + : blob_data_(new BlobDataBuilder("uuid")), expected_status_code_(0) {} + + void SetUp() override { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + temp_file1_ = temp_dir_.GetPath().AppendASCII("BlobFile1.dat"); + ASSERT_EQ(static_cast<int>(arraysize(kTestFileData1) - 1), + base::WriteFile(temp_file1_, kTestFileData1, + arraysize(kTestFileData1) - 1)); + base::File::Info file_info1; + base::GetFileInfo(temp_file1_, &file_info1); + temp_file_modification_time1_ = file_info1.last_modified; + + temp_file2_ = temp_dir_.GetPath().AppendASCII("BlobFile2.dat"); + ASSERT_EQ(static_cast<int>(arraysize(kTestFileData2) - 1), + base::WriteFile(temp_file2_, kTestFileData2, + arraysize(kTestFileData2) - 1)); + base::File::Info file_info2; + base::GetFileInfo(temp_file2_, &file_info2); + temp_file_modification_time2_ = file_info2.last_modified; + + disk_cache_backend_ = CreateInMemoryDiskCache(); + disk_cache_entry_ = CreateDiskCacheEntry( + disk_cache_backend_.get(), kTestDiskCacheKey1, kTestDiskCacheData1); + + url_request_job_factory_.SetProtocolHandler( + "blob", base::MakeUnique<MockProtocolHandler>(this)); + url_request_context_.set_job_factory(&url_request_job_factory_); + } + + void TearDown() override { + blob_handle_.reset(); + request_.reset(); + // Clean up for ASAN + base::RunLoop run_loop; + run_loop.RunUntilIdle(); + } + + void SetUpFileSystem() { + // Prepare file system. + file_system_context_ = + CreateFileSystemContextForTesting(NULL, temp_dir_.GetPath()); + + file_system_context_->OpenFileSystem( + GURL(kFileSystemURLOrigin), kFileSystemType, + storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&BlobURLRequestJobTest::OnValidateFileSystem, + base::Unretained(this))); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(file_system_root_url_.is_valid()); + + // Prepare files on file system. + const char kFilename1[] = "FileSystemFile1.dat"; + temp_file_system_file1_ = GetFileSystemURL(kFilename1); + WriteFileSystemFile(kFilename1, kTestFileSystemFileData1, + arraysize(kTestFileSystemFileData1) - 1, + &temp_file_system_file_modification_time1_); + const char kFilename2[] = "FileSystemFile2.dat"; + temp_file_system_file2_ = GetFileSystemURL(kFilename2); + WriteFileSystemFile(kFilename2, kTestFileSystemFileData2, + arraysize(kTestFileSystemFileData2) - 1, + &temp_file_system_file_modification_time2_); + } + + GURL GetFileSystemURL(const std::string& filename) { + return GURL(file_system_root_url_.spec() + filename); + } + + void WriteFileSystemFile(const std::string& filename, + const char* buf, + int buf_size, + base::Time* modification_time) { + storage::FileSystemURL url = + file_system_context_->CreateCrackedFileSystemURL( + GURL(kFileSystemURLOrigin), kFileSystemType, + base::FilePath().AppendASCII(filename)); + + ASSERT_EQ(base::File::FILE_OK, + content::AsyncFileTestHelper::CreateFileWithData( + file_system_context_.get(), url, buf, buf_size)); + + base::File::Info file_info; + ASSERT_EQ(base::File::FILE_OK, + content::AsyncFileTestHelper::GetMetadata( + file_system_context_.get(), url, &file_info)); + if (modification_time) + *modification_time = file_info.last_modified; + } + + void OnValidateFileSystem(const GURL& root, + const std::string& name, + base::File::Error result) { + ASSERT_EQ(base::File::FILE_OK, result); + ASSERT_TRUE(root.is_valid()); + file_system_root_url_ = root; + } + + void TestSuccessNonrangeRequest(const std::string& expected_response, + int64_t expected_content_length) { + expected_status_code_ = 200; + expected_response_ = expected_response; + TestRequest("GET", net::HttpRequestHeaders()); + EXPECT_EQ(expected_content_length, + request_->response_headers()->GetContentLength()); + } + + void TestErrorRequest(int expected_status_code) { + expected_status_code_ = expected_status_code; + expected_response_ = ""; + TestRequest("GET", net::HttpRequestHeaders()); + EXPECT_FALSE(request_->response_info().metadata); + } + + void TestRequest(const std::string& method, + const net::HttpRequestHeaders& extra_headers) { + request_ = url_request_context_.CreateRequest( + GURL("blob:blah"), net::DEFAULT_PRIORITY, &url_request_delegate_); + request_->set_method(method); + if (!extra_headers.IsEmpty()) + request_->SetExtraRequestHeaders(extra_headers); + request_->Start(); + + base::RunLoop().Run(); + + // Verify response. + EXPECT_EQ(net::OK, url_request_delegate_.request_status()); + EXPECT_EQ(expected_status_code_, + request_->response_headers()->response_code()); + EXPECT_EQ(expected_response_, url_request_delegate_.data_received()); + } + + void BuildComplicatedData(std::string* expected_result) { + blob_data_->AppendData(kTestData1 + 1, 2); + *expected_result = std::string(kTestData1 + 1, 2); + + blob_data_->AppendFile(temp_file1_, 2, 3, temp_file_modification_time1_); + *expected_result += std::string(kTestFileData1 + 2, 3); + + blob_data_->AppendDiskCacheEntry(new EmptyDataHandle(), + disk_cache_entry_.get(), + kTestDiskCacheStreamIndex); + *expected_result += std::string(kTestDiskCacheData1); + + blob_data_->AppendFileSystemFile(temp_file_system_file1_, 3, 4, + temp_file_system_file_modification_time1_); + *expected_result += std::string(kTestFileSystemFileData1 + 3, 4); + + blob_data_->AppendData(kTestData2 + 4, 5); + *expected_result += std::string(kTestData2 + 4, 5); + + blob_data_->AppendFile(temp_file2_, 5, 6, temp_file_modification_time2_); + *expected_result += std::string(kTestFileData2 + 5, 6); + + blob_data_->AppendFileSystemFile(temp_file_system_file2_, 6, 7, + temp_file_system_file_modification_time2_); + *expected_result += std::string(kTestFileSystemFileData2 + 6, 7); + } + + storage::BlobDataHandle* GetHandleFromBuilder() { + if (!blob_handle_) { + blob_handle_ = blob_context_.AddFinishedBlob(blob_data_.get()); + } + return blob_handle_.get(); + } + + // This only works if all the Blob items have a definite pre-computed length. + // Otherwise, this will fail a CHECK. + int64_t GetTotalBlobLength() { + int64_t total = 0; + std::unique_ptr<BlobDataSnapshot> data = + GetHandleFromBuilder()->CreateSnapshot(); + const auto& items = data->items(); + for (const auto& item : items) { + int64_t length = base::checked_cast<int64_t>(item->length()); + CHECK(length <= std::numeric_limits<int64_t>::max() - total); + total += length; + } + return total; + } + + protected: + base::ScopedTempDir temp_dir_; + base::FilePath temp_file1_; + base::FilePath temp_file2_; + base::Time temp_file_modification_time1_; + base::Time temp_file_modification_time2_; + GURL file_system_root_url_; + GURL temp_file_system_file1_; + GURL temp_file_system_file2_; + base::Time temp_file_system_file_modification_time1_; + base::Time temp_file_system_file_modification_time2_; + + std::unique_ptr<disk_cache::Backend> disk_cache_backend_; + disk_cache::ScopedEntryPtr disk_cache_entry_; + + base::MessageLoopForIO message_loop_; + scoped_refptr<storage::FileSystemContext> file_system_context_; + + storage::BlobStorageContext blob_context_; + std::unique_ptr<storage::BlobDataHandle> blob_handle_; + std::unique_ptr<BlobDataBuilder> blob_data_; + std::unique_ptr<BlobDataSnapshot> blob_data_snapshot_; + net::URLRequestJobFactoryImpl url_request_job_factory_; + net::URLRequestContext url_request_context_; + net::TestDelegate url_request_delegate_; + std::unique_ptr<net::URLRequest> request_; + + int expected_status_code_; + std::string expected_response_; +}; + +TEST_F(BlobURLRequestJobTest, TestGetSimpleDataRequest) { + blob_data_->AppendData(kTestData1); + TestSuccessNonrangeRequest(kTestData1, arraysize(kTestData1) - 1); +} + +TEST_F(BlobURLRequestJobTest, TestGetSimpleFileRequest) { + blob_data_->AppendFile(temp_file1_, 0, std::numeric_limits<uint64_t>::max(), + base::Time()); + TestSuccessNonrangeRequest(kTestFileData1, arraysize(kTestFileData1) - 1); +} + +TEST_F(BlobURLRequestJobTest, TestGetLargeFileRequest) { + base::FilePath large_temp_file = + temp_dir_.GetPath().AppendASCII("LargeBlob.dat"); + std::string large_data; + large_data.reserve(kBufferSize * 5); + for (int i = 0; i < kBufferSize * 5; ++i) + large_data.append(1, static_cast<char>(i % 256)); + ASSERT_EQ( + static_cast<int>(large_data.size()), + base::WriteFile(large_temp_file, large_data.data(), large_data.size())); + blob_data_->AppendFile(large_temp_file, 0, + std::numeric_limits<uint64_t>::max(), base::Time()); + TestSuccessNonrangeRequest(large_data, large_data.size()); +} + +TEST_F(BlobURLRequestJobTest, TestGetNonExistentFileRequest) { + base::FilePath non_existent_file = + temp_file1_.InsertBeforeExtension(FILE_PATH_LITERAL("-na")); + blob_data_->AppendFile(non_existent_file, 0, + std::numeric_limits<uint64_t>::max(), base::Time()); + TestErrorRequest(404); +} + +TEST_F(BlobURLRequestJobTest, TestGetChangedFileRequest) { + base::Time old_time = + temp_file_modification_time1_ - base::TimeDelta::FromSeconds(10); + blob_data_->AppendFile(temp_file1_, 0, 3, old_time); + TestErrorRequest(404); +} + +TEST_F(BlobURLRequestJobTest, TestGetSlicedFileRequest) { + blob_data_->AppendFile(temp_file1_, 2, 4, temp_file_modification_time1_); + std::string result(kTestFileData1 + 2, 4); + TestSuccessNonrangeRequest(result, 4); +} + +TEST_F(BlobURLRequestJobTest, TestGetSimpleFileSystemFileRequest) { + SetUpFileSystem(); + blob_data_->AppendFileSystemFile(temp_file_system_file1_, 0, + std::numeric_limits<uint64_t>::max(), + base::Time()); + TestSuccessNonrangeRequest(kTestFileSystemFileData1, + arraysize(kTestFileSystemFileData1) - 1); +} + +TEST_F(BlobURLRequestJobTest, TestGetLargeFileSystemFileRequest) { + SetUpFileSystem(); + std::string large_data; + large_data.reserve(kBufferSize * 5); + for (int i = 0; i < kBufferSize * 5; ++i) + large_data.append(1, static_cast<char>(i % 256)); + + const char kFilename[] = "LargeBlob.dat"; + WriteFileSystemFile(kFilename, large_data.data(), large_data.size(), NULL); + + blob_data_->AppendFileSystemFile(GetFileSystemURL(kFilename), 0, + std::numeric_limits<uint64_t>::max(), + base::Time()); + TestSuccessNonrangeRequest(large_data, large_data.size()); +} + +TEST_F(BlobURLRequestJobTest, TestGetNonExistentFileSystemFileRequest) { + SetUpFileSystem(); + GURL non_existent_file = GetFileSystemURL("non-existent.dat"); + blob_data_->AppendFileSystemFile( + non_existent_file, 0, std::numeric_limits<uint64_t>::max(), base::Time()); + TestErrorRequest(404); +} + +TEST_F(BlobURLRequestJobTest, TestGetInvalidFileSystemFileRequest) { + SetUpFileSystem(); + GURL invalid_file; + blob_data_->AppendFileSystemFile( + invalid_file, 0, std::numeric_limits<uint64_t>::max(), base::Time()); + TestErrorRequest(500); +} + +TEST_F(BlobURLRequestJobTest, TestGetChangedFileSystemFileRequest) { + SetUpFileSystem(); + base::Time old_time = temp_file_system_file_modification_time1_ - + base::TimeDelta::FromSeconds(10); + blob_data_->AppendFileSystemFile(temp_file_system_file1_, 0, 3, old_time); + TestErrorRequest(404); +} + +TEST_F(BlobURLRequestJobTest, TestGetSlicedFileSystemFileRequest) { + SetUpFileSystem(); + blob_data_->AppendFileSystemFile(temp_file_system_file1_, 2, 4, + temp_file_system_file_modification_time1_); + std::string result(kTestFileSystemFileData1 + 2, 4); + TestSuccessNonrangeRequest(result, 4); +} + +TEST_F(BlobURLRequestJobTest, TestGetSimpleDiskCacheRequest) { + blob_data_->AppendDiskCacheEntry(new EmptyDataHandle(), + disk_cache_entry_.get(), + kTestDiskCacheStreamIndex); + TestSuccessNonrangeRequest(kTestDiskCacheData1, + arraysize(kTestDiskCacheData1) - 1); +} + +TEST_F(BlobURLRequestJobTest, TestGetComplicatedDataFileAndDiskCacheRequest) { + SetUpFileSystem(); + std::string result; + BuildComplicatedData(&result); + TestSuccessNonrangeRequest(result, GetTotalBlobLength()); +} + +TEST_F(BlobURLRequestJobTest, TestGetRangeRequest1) { + SetUpFileSystem(); + std::string result; + BuildComplicatedData(&result); + net::HttpRequestHeaders extra_headers; + extra_headers.SetHeader(net::HttpRequestHeaders::kRange, + net::HttpByteRange::Bounded(5, 10).GetHeaderValue()); + expected_status_code_ = 206; + expected_response_ = result.substr(5, 10 - 5 + 1); + TestRequest("GET", extra_headers); + + EXPECT_EQ(6, request_->response_headers()->GetContentLength()); + EXPECT_FALSE(request_->response_info().metadata); + + int64_t first = 0, last = 0, length = 0; + EXPECT_TRUE(request_->response_headers()->GetContentRangeFor206(&first, &last, + &length)); + EXPECT_EQ(5, first); + EXPECT_EQ(10, last); + EXPECT_EQ(GetTotalBlobLength(), length); +} + +TEST_F(BlobURLRequestJobTest, TestGetRangeRequest2) { + SetUpFileSystem(); + std::string result; + BuildComplicatedData(&result); + net::HttpRequestHeaders extra_headers; + extra_headers.SetHeader(net::HttpRequestHeaders::kRange, + net::HttpByteRange::Suffix(10).GetHeaderValue()); + expected_status_code_ = 206; + expected_response_ = result.substr(result.length() - 10); + TestRequest("GET", extra_headers); + + EXPECT_EQ(10, request_->response_headers()->GetContentLength()); + EXPECT_FALSE(request_->response_info().metadata); + + int64_t total = GetTotalBlobLength(); + int64_t first = 0, last = 0, length = 0; + EXPECT_TRUE(request_->response_headers()->GetContentRangeFor206(&first, &last, + &length)); + EXPECT_EQ(total - 10, first); + EXPECT_EQ(total - 1, last); + EXPECT_EQ(total, length); +} + +TEST_F(BlobURLRequestJobTest, TestGetRangeRequest3) { + SetUpFileSystem(); + std::string result; + BuildComplicatedData(&result); + net::HttpRequestHeaders extra_headers; + extra_headers.SetHeader(net::HttpRequestHeaders::kRange, + net::HttpByteRange::Bounded(0, 2).GetHeaderValue()); + expected_status_code_ = 206; + expected_response_ = result.substr(0, 3); + TestRequest("GET", extra_headers); + + EXPECT_EQ(3, request_->response_headers()->GetContentLength()); + EXPECT_FALSE(request_->response_info().metadata); + + int64_t first = 0, last = 0, length = 0; + EXPECT_TRUE(request_->response_headers()->GetContentRangeFor206(&first, &last, + &length)); + EXPECT_EQ(0, first); + EXPECT_EQ(2, last); + EXPECT_EQ(GetTotalBlobLength(), length); +} + +TEST_F(BlobURLRequestJobTest, TestExtraHeaders) { + blob_data_->set_content_type(kTestContentType); + blob_data_->set_content_disposition(kTestContentDisposition); + blob_data_->AppendData(kTestData1); + expected_status_code_ = 200; + expected_response_ = kTestData1; + TestRequest("GET", net::HttpRequestHeaders()); + + std::string content_type; + EXPECT_TRUE(request_->response_headers()->GetMimeType(&content_type)); + EXPECT_EQ(kTestContentType, content_type); + EXPECT_FALSE(request_->response_info().metadata); + size_t iter = 0; + std::string content_disposition; + EXPECT_TRUE(request_->response_headers()->EnumerateHeader( + &iter, "Content-Disposition", &content_disposition)); + EXPECT_EQ(kTestContentDisposition, content_disposition); +} + +TEST_F(BlobURLRequestJobTest, TestSideData) { + disk_cache::ScopedEntryPtr disk_cache_entry_with_side_data = + CreateDiskCacheEntryWithSideData(disk_cache_backend_.get(), + kTestDiskCacheKey2, kTestDiskCacheData2, + kTestDiskCacheSideData); + blob_data_->AppendDiskCacheEntryWithSideData( + new EmptyDataHandle(), disk_cache_entry_with_side_data.get(), + kTestDiskCacheStreamIndex, kTestDiskCacheSideStreamIndex); + expected_status_code_ = 200; + expected_response_ = kTestDiskCacheData2; + TestRequest("GET", net::HttpRequestHeaders()); + EXPECT_EQ(static_cast<int>(arraysize(kTestDiskCacheData2) - 1), + request_->response_headers()->GetContentLength()); + + ASSERT_TRUE(request_->response_info().metadata); + std::string metadata(request_->response_info().metadata->data(), + request_->response_info().metadata->size()); + EXPECT_EQ(std::string(kTestDiskCacheSideData), metadata); +} + +TEST_F(BlobURLRequestJobTest, TestZeroSizeSideData) { + disk_cache::ScopedEntryPtr disk_cache_entry_with_side_data = + CreateDiskCacheEntryWithSideData(disk_cache_backend_.get(), + kTestDiskCacheKey2, kTestDiskCacheData2, + ""); + blob_data_->AppendDiskCacheEntryWithSideData( + new EmptyDataHandle(), disk_cache_entry_with_side_data.get(), + kTestDiskCacheStreamIndex, kTestDiskCacheSideStreamIndex); + expected_status_code_ = 200; + expected_response_ = kTestDiskCacheData2; + TestRequest("GET", net::HttpRequestHeaders()); + EXPECT_EQ(static_cast<int>(arraysize(kTestDiskCacheData2) - 1), + request_->response_headers()->GetContentLength()); + + EXPECT_FALSE(request_->response_info().metadata); +} + +TEST_F(BlobURLRequestJobTest, BrokenBlob) { + blob_handle_ = blob_context_.AddBrokenBlob( + "uuid", "", "", storage::BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS); + TestErrorRequest(500); +} + +} // namespace content diff --git a/chromium/storage/browser/blob/shareable_file_reference.cc b/chromium/storage/browser/blob/shareable_file_reference.cc index c8672f76d44..b1ccc378c05 100644 --- a/chromium/storage/browser/blob/shareable_file_reference.cc +++ b/chromium/storage/browser/blob/shareable_file_reference.cc @@ -55,7 +55,8 @@ class ShareableFileMap : public base::NonThreadSafe { DISALLOW_COPY_AND_ASSIGN(ShareableFileMap); }; -base::LazyInstance<ShareableFileMap> g_file_map = LAZY_INSTANCE_INITIALIZER; +base::LazyInstance<ShareableFileMap>::DestructorAtExit g_file_map = + LAZY_INSTANCE_INITIALIZER; } // namespace diff --git a/chromium/storage/browser/crbug653751_unittest.cc b/chromium/storage/browser/crbug653751_unittest.cc deleted file mode 100644 index 44b86fb6ee4..00000000000 --- a/chromium/storage/browser/crbug653751_unittest.cc +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2016 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 "testing/gtest/include/gtest/gtest.h" - -// This file will be removed when the real storage unit tests are migrated over -// from content_unittests to storage_unittests. Until then, it is needed to keep -// the win_clang builder happy. See http://crbug.com/653751 - -TEST(StorageUnittests, EmptyTest) { - -} diff --git a/chromium/storage/browser/database/OWNERS b/chromium/storage/browser/database/OWNERS new file mode 100644 index 00000000000..07618c5c7d3 --- /dev/null +++ b/chromium/storage/browser/database/OWNERS @@ -0,0 +1,4 @@ +pwnall@chromium.org + +# TEAM: storage-dev@chromium.org +# COMPONENT: Blink>Storage>WebSQL diff --git a/chromium/storage/browser/database/database_quota_client_unittest.cc b/chromium/storage/browser/database/database_quota_client_unittest.cc new file mode 100644 index 00000000000..db914fc69e2 --- /dev/null +++ b/chromium/storage/browser/database/database_quota_client_unittest.cc @@ -0,0 +1,277 @@ +// Copyright 2014 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 <stdint.h> + +#include <map> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/location.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/scoped_task_environment.h" +#include "base/threading/thread_task_runner_handle.h" +#include "net/base/completion_callback.h" +#include "net/base/net_errors.h" +#include "storage/browser/database/database_quota_client.h" +#include "storage/browser/database/database_tracker.h" +#include "storage/browser/database/database_util.h" +#include "storage/common/database/database_identifier.h" +#include "testing/gtest/include/gtest/gtest.h" + +using storage::DatabaseQuotaClient; +using storage::DatabaseTracker; +using storage::OriginInfo; + +namespace content { + +// Declared to shorten the line lengths. +static const storage::StorageType kTemp = storage::kStorageTypeTemporary; +static const storage::StorageType kPerm = storage::kStorageTypePersistent; + +// Mock tracker class the mocks up those methods of the tracker +// that are used by the QuotaClient. +class MockDatabaseTracker : public DatabaseTracker { + public: + MockDatabaseTracker() + : DatabaseTracker(base::FilePath(), false, NULL, NULL, NULL), + delete_called_count_(0), + async_delete_(false) {} + + bool GetOriginInfo(const std::string& origin_identifier, + OriginInfo* info) override { + std::map<GURL, MockOriginInfo>::const_iterator found = + mock_origin_infos_.find( + storage::GetOriginFromIdentifier(origin_identifier)); + if (found == mock_origin_infos_.end()) + return false; + *info = OriginInfo(found->second); + return true; + } + + bool GetAllOriginIdentifiers( + std::vector<std::string>* origins_identifiers) override { + std::map<GURL, MockOriginInfo>::const_iterator iter; + for (iter = mock_origin_infos_.begin(); iter != mock_origin_infos_.end(); + ++iter) { + origins_identifiers->push_back(iter->second.GetOriginIdentifier()); + } + return true; + } + + bool GetAllOriginsInfo(std::vector<OriginInfo>* origins_info) override { + std::map<GURL, MockOriginInfo>::const_iterator iter; + for (iter = mock_origin_infos_.begin(); iter != mock_origin_infos_.end(); + ++iter) { + origins_info->push_back(OriginInfo(iter->second)); + } + return true; + } + + int DeleteDataForOrigin(const std::string& origin_identifier, + const net::CompletionCallback& callback) override { + ++delete_called_count_; + if (async_delete()) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&MockDatabaseTracker::AsyncDeleteDataForOrigin, + this, callback)); + return net::ERR_IO_PENDING; + } + return net::OK; + } + + void AsyncDeleteDataForOrigin(const net::CompletionCallback& callback) { + callback.Run(net::OK); + } + + void AddMockDatabase(const GURL& origin, const char* name, int size) { + MockOriginInfo& info = mock_origin_infos_[origin]; + info.set_origin(storage::GetIdentifierFromOrigin(origin)); + info.AddMockDatabase(base::ASCIIToUTF16(name), size); + } + + int delete_called_count() { return delete_called_count_; } + bool async_delete() { return async_delete_; } + void set_async_delete(bool async) { async_delete_ = async; } + + protected: + ~MockDatabaseTracker() override {} + + private: + class MockOriginInfo : public OriginInfo { + public: + void set_origin(const std::string& origin_identifier) { + origin_identifier_ = origin_identifier; + } + + void AddMockDatabase(const base::string16& name, int size) { + EXPECT_TRUE(database_info_.find(name) == database_info_.end()); + database_info_[name].first = size; + total_size_ += size; + } + }; + + int delete_called_count_; + bool async_delete_; + std::map<GURL, MockOriginInfo> mock_origin_infos_; +}; + +// Base class for our test fixtures. +class DatabaseQuotaClientTest : public testing::Test { + public: + const GURL kOriginA; + const GURL kOriginB; + const GURL kOriginOther; + + DatabaseQuotaClientTest() + : kOriginA("http://host"), + kOriginB("http://host:8000"), + kOriginOther("http://other"), + usage_(0), + mock_tracker_(new MockDatabaseTracker), + weak_factory_(this) {} + + int64_t GetOriginUsage(storage::QuotaClient* client, + const GURL& origin, + storage::StorageType type) { + usage_ = 0; + client->GetOriginUsage( + origin, type, + base::Bind(&DatabaseQuotaClientTest::OnGetOriginUsageComplete, + weak_factory_.GetWeakPtr())); + base::RunLoop().RunUntilIdle(); + return usage_; + } + + const std::set<GURL>& GetOriginsForType(storage::QuotaClient* client, + storage::StorageType type) { + origins_.clear(); + client->GetOriginsForType( + type, base::Bind(&DatabaseQuotaClientTest::OnGetOriginsComplete, + weak_factory_.GetWeakPtr())); + base::RunLoop().RunUntilIdle(); + return origins_; + } + + const std::set<GURL>& GetOriginsForHost(storage::QuotaClient* client, + storage::StorageType type, + const std::string& host) { + origins_.clear(); + client->GetOriginsForHost( + type, host, + base::Bind(&DatabaseQuotaClientTest::OnGetOriginsComplete, + weak_factory_.GetWeakPtr())); + base::RunLoop().RunUntilIdle(); + return origins_; + } + + bool DeleteOriginData(storage::QuotaClient* client, + storage::StorageType type, + const GURL& origin) { + delete_status_ = storage::kQuotaStatusUnknown; + client->DeleteOriginData( + origin, type, + base::Bind(&DatabaseQuotaClientTest::OnDeleteOriginDataComplete, + weak_factory_.GetWeakPtr())); + base::RunLoop().RunUntilIdle(); + return delete_status_ == storage::kQuotaStatusOk; + } + + MockDatabaseTracker* mock_tracker() { return mock_tracker_.get(); } + + private: + void OnGetOriginUsageComplete(int64_t usage) { usage_ = usage; } + + void OnGetOriginsComplete(const std::set<GURL>& origins) { + origins_ = origins; + } + + void OnDeleteOriginDataComplete(storage::QuotaStatusCode status) { + delete_status_ = status; + } + + base::test::ScopedTaskEnvironment scoped_task_environment_; + int64_t usage_; + std::set<GURL> origins_; + storage::QuotaStatusCode delete_status_; + scoped_refptr<MockDatabaseTracker> mock_tracker_; + base::WeakPtrFactory<DatabaseQuotaClientTest> weak_factory_; +}; + +TEST_F(DatabaseQuotaClientTest, GetOriginUsage) { + DatabaseQuotaClient client(base::ThreadTaskRunnerHandle::Get().get(), + mock_tracker()); + + EXPECT_EQ(0, GetOriginUsage(&client, kOriginA, kTemp)); + EXPECT_EQ(0, GetOriginUsage(&client, kOriginA, kPerm)); + + mock_tracker()->AddMockDatabase(kOriginA, "fooDB", 1000); + EXPECT_EQ(1000, GetOriginUsage(&client, kOriginA, kTemp)); + EXPECT_EQ(0, GetOriginUsage(&client, kOriginA, kPerm)); + + EXPECT_EQ(0, GetOriginUsage(&client, kOriginB, kPerm)); + EXPECT_EQ(0, GetOriginUsage(&client, kOriginB, kTemp)); +} + +TEST_F(DatabaseQuotaClientTest, GetOriginsForHost) { + DatabaseQuotaClient client(base::ThreadTaskRunnerHandle::Get().get(), + mock_tracker()); + + EXPECT_EQ(kOriginA.host(), kOriginB.host()); + EXPECT_NE(kOriginA.host(), kOriginOther.host()); + + std::set<GURL> origins = GetOriginsForHost(&client, kTemp, kOriginA.host()); + EXPECT_TRUE(origins.empty()); + + mock_tracker()->AddMockDatabase(kOriginA, "fooDB", 1000); + origins = GetOriginsForHost(&client, kTemp, kOriginA.host()); + EXPECT_EQ(origins.size(), 1ul); + EXPECT_TRUE(origins.find(kOriginA) != origins.end()); + + mock_tracker()->AddMockDatabase(kOriginB, "barDB", 1000); + origins = GetOriginsForHost(&client, kTemp, kOriginA.host()); + EXPECT_EQ(origins.size(), 2ul); + EXPECT_TRUE(origins.find(kOriginA) != origins.end()); + EXPECT_TRUE(origins.find(kOriginB) != origins.end()); + + EXPECT_TRUE(GetOriginsForHost(&client, kPerm, kOriginA.host()).empty()); + EXPECT_TRUE(GetOriginsForHost(&client, kTemp, kOriginOther.host()).empty()); +} + +TEST_F(DatabaseQuotaClientTest, GetOriginsForType) { + DatabaseQuotaClient client(base::ThreadTaskRunnerHandle::Get().get(), + mock_tracker()); + + EXPECT_TRUE(GetOriginsForType(&client, kTemp).empty()); + EXPECT_TRUE(GetOriginsForType(&client, kPerm).empty()); + + mock_tracker()->AddMockDatabase(kOriginA, "fooDB", 1000); + std::set<GURL> origins = GetOriginsForType(&client, kTemp); + EXPECT_EQ(origins.size(), 1ul); + EXPECT_TRUE(origins.find(kOriginA) != origins.end()); + + EXPECT_TRUE(GetOriginsForType(&client, kPerm).empty()); +} + +TEST_F(DatabaseQuotaClientTest, DeleteOriginData) { + DatabaseQuotaClient client(base::ThreadTaskRunnerHandle::Get().get(), + mock_tracker()); + + // Perm deletions are short circuited in the Client and + // should not reach the DatabaseTracker. + EXPECT_TRUE(DeleteOriginData(&client, kPerm, kOriginA)); + EXPECT_EQ(0, mock_tracker()->delete_called_count()); + + mock_tracker()->set_async_delete(false); + EXPECT_TRUE(DeleteOriginData(&client, kTemp, kOriginA)); + EXPECT_EQ(1, mock_tracker()->delete_called_count()); + + mock_tracker()->set_async_delete(true); + EXPECT_TRUE(DeleteOriginData(&client, kTemp, kOriginA)); + EXPECT_EQ(2, mock_tracker()->delete_called_count()); +} + +} // namespace content diff --git a/chromium/storage/browser/database/database_util_unittest.cc b/chromium/storage/browser/database/database_util_unittest.cc new file mode 100644 index 00000000000..6aa9f6ee0d5 --- /dev/null +++ b/chromium/storage/browser/database/database_util_unittest.cc @@ -0,0 +1,49 @@ +// Copyright 2014 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 "storage/browser/database/database_util.h" +#include "base/strings/string_piece.h" +#include "base/strings/utf_string_conversions.h" +#include "storage/common/database/database_identifier.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::ASCIIToUTF16; +using storage::DatabaseUtil; + +static void TestVfsFilePath(bool expected_result, + const char* vfs_file_name, + const char* expected_origin_identifier = "", + const char* expected_database_name = "", + const char* expected_sqlite_suffix = "") { + std::string origin_identifier; + base::string16 database_name; + base::string16 sqlite_suffix; + EXPECT_EQ(expected_result, + DatabaseUtil::CrackVfsFileName(ASCIIToUTF16(vfs_file_name), + &origin_identifier, &database_name, + &sqlite_suffix)); + EXPECT_EQ(expected_origin_identifier, origin_identifier); + EXPECT_EQ(ASCIIToUTF16(expected_database_name), database_name); + EXPECT_EQ(ASCIIToUTF16(expected_sqlite_suffix), sqlite_suffix); +} + +namespace content { + +// Test DatabaseUtil::CrackVfsFilePath on various inputs. +TEST(DatabaseUtilTest, CrackVfsFilePathTest) { + TestVfsFilePath(true, "http_origin_0/#", "http_origin_0", "", ""); + TestVfsFilePath(true, "http_origin_0/#suffix", "http_origin_0", "", "suffix"); + TestVfsFilePath(true, "http_origin_0/db_name#", "http_origin_0", "db_name", + ""); + TestVfsFilePath(true, "http_origin_0/db_name#suffix", "http_origin_0", + "db_name", "suffix"); + TestVfsFilePath(false, "http_origin_0db_name#"); + TestVfsFilePath(false, "http_origin_0db_name#suffix"); + TestVfsFilePath(false, "http_origin_0/db_name"); + TestVfsFilePath(false, "http_origin_0#db_name/suffix"); + TestVfsFilePath(false, "/db_name#"); + TestVfsFilePath(false, "/db_name#suffix"); +} + +} // namespace content diff --git a/chromium/storage/browser/database/databases_table_unittest.cc b/chromium/storage/browser/database/databases_table_unittest.cc new file mode 100644 index 00000000000..c26e92154e5 --- /dev/null +++ b/chromium/storage/browser/database/databases_table_unittest.cc @@ -0,0 +1,148 @@ +// Copyright 2014 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 <stddef.h> + +#include "base/bind.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "sql/connection.h" +#include "sql/statement.h" +#include "sql/test/scoped_error_expecter.h" +#include "storage/browser/database/databases_table.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/sqlite/sqlite3.h" + +using base::ASCIIToUTF16; +using storage::DatabaseDetails; +using storage::DatabasesTable; + +namespace content { + +static void CheckDetailsAreEqual(const DatabaseDetails& d1, + const DatabaseDetails& d2) { + EXPECT_EQ(d1.origin_identifier, d2.origin_identifier); + EXPECT_EQ(d1.database_name, d2.database_name); + EXPECT_EQ(d1.description, d2.description); + EXPECT_EQ(d1.estimated_size, d2.estimated_size); +} + +static bool DatabasesTableIsEmpty(sql::Connection* db) { + sql::Statement statement( + db->GetCachedStatement(SQL_FROM_HERE, "SELECT COUNT(*) FROM Databases")); + return (statement.is_valid() && statement.Step() && !statement.ColumnInt(0)); +} + +TEST(DatabasesTableTest, TestIt) { + // Initialize the 'Databases' table. + sql::Connection db; + + sql::test::ScopedErrorExpecter expecter; + // TODO(shess): Suppressing SQLITE_CONSTRAINT because the code + // expects that and handles the resulting error. Consider revising + // the code to use INSERT OR IGNORE (which would not throw + // SQLITE_CONSTRAINT) and then check ChangeCount() to see if any + // changes were made. + expecter.ExpectError(SQLITE_CONSTRAINT); + + // Initialize the temp dir and the 'Databases' table. + EXPECT_TRUE(db.OpenInMemory()); + DatabasesTable databases_table(&db); + EXPECT_TRUE(databases_table.Init()); + + // The 'Databases' table should be empty. + EXPECT_TRUE(DatabasesTableIsEmpty(&db)); + + // Create the details for a databases. + DatabaseDetails details_in1; + DatabaseDetails details_out1; + details_in1.origin_identifier = "origin1"; + details_in1.database_name = ASCIIToUTF16("db1"); + details_in1.description = ASCIIToUTF16("description_db1"); + details_in1.estimated_size = 100; + + // Updating details for this database should fail. + EXPECT_FALSE(databases_table.UpdateDatabaseDetails(details_in1)); + EXPECT_FALSE(databases_table.GetDatabaseDetails( + details_in1.origin_identifier, details_in1.database_name, &details_out1)); + + // Inserting details for this database should pass. + EXPECT_TRUE(databases_table.InsertDatabaseDetails(details_in1)); + EXPECT_TRUE(databases_table.GetDatabaseDetails( + details_in1.origin_identifier, details_in1.database_name, &details_out1)); + EXPECT_EQ(1, databases_table.GetDatabaseID(details_in1.origin_identifier, + details_in1.database_name)); + + // Check that the details were correctly written to the database. + CheckDetailsAreEqual(details_in1, details_out1); + + // Check that inserting a duplicate row fails. + EXPECT_FALSE(databases_table.InsertDatabaseDetails(details_in1)); + + // Insert details for another database with the same origin. + DatabaseDetails details_in2; + details_in2.origin_identifier = "origin1"; + details_in2.database_name = ASCIIToUTF16("db2"); + details_in2.description = ASCIIToUTF16("description_db2"); + details_in2.estimated_size = 200; + EXPECT_TRUE(databases_table.InsertDatabaseDetails(details_in2)); + EXPECT_EQ(2, databases_table.GetDatabaseID(details_in2.origin_identifier, + details_in2.database_name)); + + // Insert details for a third database with a different origin. + DatabaseDetails details_in3; + details_in3.origin_identifier = "origin2"; + details_in3.database_name = ASCIIToUTF16("db3"); + details_in3.description = ASCIIToUTF16("description_db3"); + details_in3.estimated_size = 300; + EXPECT_TRUE(databases_table.InsertDatabaseDetails(details_in3)); + EXPECT_EQ(3, databases_table.GetDatabaseID(details_in3.origin_identifier, + details_in3.database_name)); + + // There should be no database with origin "origin3". + std::vector<DatabaseDetails> details_out_origin3; + EXPECT_TRUE(databases_table.GetAllDatabaseDetailsForOriginIdentifier( + "origin3", &details_out_origin3)); + EXPECT_TRUE(details_out_origin3.empty()); + + // There should be only two databases with origin "origin1". + std::vector<DatabaseDetails> details_out_origin1; + EXPECT_TRUE(databases_table.GetAllDatabaseDetailsForOriginIdentifier( + details_in1.origin_identifier, &details_out_origin1)); + EXPECT_EQ(size_t(2), details_out_origin1.size()); + CheckDetailsAreEqual(details_in1, details_out_origin1[0]); + CheckDetailsAreEqual(details_in2, details_out_origin1[1]); + + // Get the list of all origins: should be "origin1" and "origin2". + std::vector<std::string> origins_out; + EXPECT_TRUE(databases_table.GetAllOriginIdentifiers(&origins_out)); + EXPECT_EQ(size_t(2), origins_out.size()); + EXPECT_EQ(details_in1.origin_identifier, origins_out[0]); + EXPECT_EQ(details_in3.origin_identifier, origins_out[1]); + + // Delete an origin and check that it's no longer in the table. + origins_out.clear(); + EXPECT_TRUE( + databases_table.DeleteOriginIdentifier(details_in3.origin_identifier)); + EXPECT_TRUE(databases_table.GetAllOriginIdentifiers(&origins_out)); + EXPECT_EQ(size_t(1), origins_out.size()); + EXPECT_EQ(details_in1.origin_identifier, origins_out[0]); + + // Deleting an origin that doesn't have any record in this table should fail. + EXPECT_FALSE(databases_table.DeleteOriginIdentifier("unknown_origin")); + + // Delete the details for 'db1' and check that they're no longer there. + EXPECT_TRUE(databases_table.DeleteDatabaseDetails( + details_in1.origin_identifier, details_in1.database_name)); + EXPECT_FALSE(databases_table.GetDatabaseDetails( + details_in1.origin_identifier, details_in1.database_name, &details_out1)); + + // Check that trying to delete a record that doesn't exist fails. + EXPECT_FALSE(databases_table.DeleteDatabaseDetails( + "unknown_origin", ASCIIToUTF16("unknown_database"))); + + ASSERT_TRUE(expecter.SawExpectedErrors()); +} + +} // namespace content diff --git a/chromium/storage/browser/fileapi/external_mount_points_unittest.cc b/chromium/storage/browser/fileapi/external_mount_points_unittest.cc new file mode 100644 index 00000000000..59be8624c40 --- /dev/null +++ b/chromium/storage/browser/fileapi/external_mount_points_unittest.cc @@ -0,0 +1,505 @@ +// Copyright 2014 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 "storage/browser/fileapi/external_mount_points.h" + +#include <stddef.h> + +#include <string> + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/common/fileapi/file_system_mount_option.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define FPL FILE_PATH_LITERAL + +#if defined(FILE_PATH_USES_DRIVE_LETTERS) +#define DRIVE FPL("C:") +#else +#define DRIVE +#endif + +using storage::FileSystemURL; + +namespace content { + +TEST(ExternalMountPointsTest, AddMountPoint) { + scoped_refptr<storage::ExternalMountPoints> mount_points( + storage::ExternalMountPoints::CreateRefCounted()); + + struct TestCase { + // The mount point's name. + const char* const name; + // The mount point's path. + const base::FilePath::CharType* const path; + // Whether the mount point registration should succeed. + bool success; + // Path returned by GetRegisteredPath. NULL if the method is expected to + // fail. + const base::FilePath::CharType* const registered_path; + }; + + const TestCase kTestCases[] = { + // Valid mount point. + { "test", DRIVE FPL("/foo/test"), true, DRIVE FPL("/foo/test") }, + // Valid mount point with only one path component. + { "bbb", DRIVE FPL("/bbb"), true, DRIVE FPL("/bbb") }, + // Existing mount point path is substring of the mount points path. + { "test11", DRIVE FPL("/foo/test11"), true, DRIVE FPL("/foo/test11") }, + // Path substring of an existing path. + { "test1", DRIVE FPL("/foo/test1"), true, DRIVE FPL("/foo/test1") }, + // Empty mount point name and path. + { "", DRIVE FPL(""), false, NULL }, + // Empty mount point name. + { "", DRIVE FPL("/ddd"), false, NULL }, + // Empty mount point path. + { "empty_path", FPL(""), true, FPL("") }, + // Name different from path's base name. + { "not_base_name", DRIVE FPL("/x/y/z"), true, DRIVE FPL("/x/y/z") }, + // References parent. + { "invalid", DRIVE FPL("../foo/invalid"), false, NULL }, + // Relative path. + { "relative", DRIVE FPL("foo/relative"), false, NULL }, + // Existing mount point path. + { "path_exists", DRIVE FPL("/foo/test"), false, NULL }, + // Mount point with the same name exists. + { "test", DRIVE FPL("/foo/a/test_name_exists"), false, + DRIVE FPL("/foo/test") }, + // Child of an existing mount point. + { "a1", DRIVE FPL("/foo/test/a"), false, NULL }, + // Parent of an existing mount point. + { "foo1", DRIVE FPL("/foo"), false, NULL }, + // Bit bigger depth. + { "g", DRIVE FPL("/foo/a/b/c/d/e/f/g"), true, + DRIVE FPL("/foo/a/b/c/d/e/f/g") }, + // Sibling mount point (with similar name) exists. + { "ff", DRIVE FPL("/foo/a/b/c/d/e/ff"), true, + DRIVE FPL("/foo/a/b/c/d/e/ff") }, + // Lexicographically last among existing mount points. + { "yyy", DRIVE FPL("/zzz/yyy"), true, DRIVE FPL("/zzz/yyy") }, + // Parent of the lexicographically last mount point. + { "zzz1", DRIVE FPL("/zzz"), false, NULL }, + // Child of the lexicographically last mount point. + { "xxx1", DRIVE FPL("/zzz/yyy/xxx"), false, NULL }, + // Lexicographically first among existing mount points. + { "b", DRIVE FPL("/a/b"), true, DRIVE FPL("/a/b") }, + // Parent of lexicographically first mount point. + { "a2", DRIVE FPL("/a"), false, NULL }, + // Child of lexicographically last mount point. + { "c1", DRIVE FPL("/a/b/c"), false, NULL }, + // Parent to all of the mount points. + { "root", DRIVE FPL("/"), false, NULL }, + // Path contains .. component. + { "funky", DRIVE FPL("/tt/fun/../funky"), false, NULL }, + // Windows separators. +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { "win", DRIVE FPL("\\try\\separators\\win"), true, + DRIVE FPL("\\try\\separators\\win") }, + { "win1", DRIVE FPL("\\try/separators\\win1"), true, + DRIVE FPL("\\try/separators\\win1") }, + { "win2", DRIVE FPL("\\try/separators\\win"), false, NULL }, +#else + { "win", DRIVE FPL("\\separators\\win"), false, NULL }, + { "win1", DRIVE FPL("\\try/separators\\win1"), false, NULL }, +#endif + // Win separators, but relative path. + { "win2", DRIVE FPL("try\\separators\\win2"), false, NULL }, + }; + + // Test adding mount points. + for (size_t i = 0; i < arraysize(kTestCases); ++i) { + EXPECT_EQ( + kTestCases[i].success, + mount_points->RegisterFileSystem(kTestCases[i].name, + storage::kFileSystemTypeNativeLocal, + storage::FileSystemMountOption(), + base::FilePath(kTestCases[i].path))) + << "Adding mount point: " << kTestCases[i].name << " with path " + << kTestCases[i].path; + } + + // Test that final mount point presence state is as expected. + for (size_t i = 0; i < arraysize(kTestCases); ++i) { + base::FilePath found_path; + EXPECT_EQ(kTestCases[i].registered_path != NULL, + mount_points->GetRegisteredPath(kTestCases[i].name, &found_path)) + << "Test case: " << i; + + if (kTestCases[i].registered_path) { + base::FilePath expected_path(kTestCases[i].registered_path); + EXPECT_EQ(expected_path.NormalizePathSeparators(), found_path); + } + } +} + +TEST(ExternalMountPointsTest, GetVirtualPath) { + scoped_refptr<storage::ExternalMountPoints> mount_points( + storage::ExternalMountPoints::CreateRefCounted()); + + mount_points->RegisterFileSystem("c", + storage::kFileSystemTypeNativeLocal, + storage::FileSystemMountOption(), + base::FilePath(DRIVE FPL("/a/b/c"))); + // Note that "/a/b/c" < "/a/b/c(1)" < "/a/b/c/". + mount_points->RegisterFileSystem("c(1)", + storage::kFileSystemTypeNativeLocal, + storage::FileSystemMountOption(), + base::FilePath(DRIVE FPL("/a/b/c(1)"))); + mount_points->RegisterFileSystem("x", + storage::kFileSystemTypeNativeLocal, + storage::FileSystemMountOption(), + base::FilePath(DRIVE FPL("/z/y/x"))); + mount_points->RegisterFileSystem("o", + storage::kFileSystemTypeNativeLocal, + storage::FileSystemMountOption(), + base::FilePath(DRIVE FPL("/m/n/o"))); + // A mount point whose name does not match its path base name. + mount_points->RegisterFileSystem("mount", + storage::kFileSystemTypeNativeLocal, + storage::FileSystemMountOption(), + base::FilePath(DRIVE FPL("/root/foo"))); + // A mount point with an empty path. + mount_points->RegisterFileSystem("empty_path", + storage::kFileSystemTypeNativeLocal, + storage::FileSystemMountOption(), + base::FilePath()); + + struct TestCase { + const base::FilePath::CharType* const local_path; + bool success; + const base::FilePath::CharType* const virtual_path; + }; + + const TestCase kTestCases[] = { + // Empty path. + { FPL(""), false, FPL("") }, + // No registered mount point (but is parent to a mount point). + { DRIVE FPL("/a/b"), false, FPL("") }, + // No registered mount point (but is parent to a mount point). + { DRIVE FPL("/z/y"), false, FPL("") }, + // No registered mount point (but is parent to a mount point). + { DRIVE FPL("/m/n"), false, FPL("") }, + // No registered mount point. + { DRIVE FPL("/foo/mount"), false, FPL("") }, + // An existing mount point path is substring. + { DRIVE FPL("/a/b/c1"), false, FPL("") }, + // No leading /. + { DRIVE FPL("a/b/c"), false, FPL("") }, + // Sibling to a root path. + { DRIVE FPL("/a/b/d/e"), false, FPL("") }, + // Sibling to a root path. + { DRIVE FPL("/z/y/v/u"), false, FPL("") }, + // Sibling to a root path. + { DRIVE FPL("/m/n/p/q"), false, FPL("") }, + // Mount point root path. + { DRIVE FPL("/a/b/c"), true, FPL("c") }, + // Mount point root path. + { DRIVE FPL("/z/y/x"), true, FPL("x") }, + // Mount point root path. + { DRIVE FPL("/m/n/o"), true, FPL("o") }, + // Mount point child path. + { DRIVE FPL("/a/b/c/d/e"), true, FPL("c/d/e") }, + // Mount point child path. + { DRIVE FPL("/z/y/x/v/u"), true, FPL("x/v/u") }, + // Mount point child path. + { DRIVE FPL("/m/n/o/p/q"), true, FPL("o/p/q") }, + // Name doesn't match mount point path base name. + { DRIVE FPL("/root/foo/a/b/c"), true, FPL("mount/a/b/c") }, + { DRIVE FPL("/root/foo"), true, FPL("mount") }, + // Mount point contains character whose ASCII code is smaller than file path + // separator's. + { DRIVE FPL("/a/b/c(1)/d/e"), true, FPL("c(1)/d/e") }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + // Path with win separators mixed in. + { DRIVE FPL("/a\\b\\c/d"), true, FPL("c/d") }, +#endif + }; + + for (size_t i = 0; i < arraysize(kTestCases); ++i) { + // Initialize virtual path with a value. + base::FilePath virtual_path(DRIVE FPL("/mount")); + base::FilePath local_path(kTestCases[i].local_path); + EXPECT_EQ(kTestCases[i].success, + mount_points->GetVirtualPath(local_path, &virtual_path)) + << "Resolving " << kTestCases[i].local_path; + + // There are no guarantees for |virtual_path| value if |GetVirtualPath| + // fails. + if (!kTestCases[i].success) + continue; + + base::FilePath expected_virtual_path(kTestCases[i].virtual_path); + EXPECT_EQ(expected_virtual_path.NormalizePathSeparators(), virtual_path) + << "Resolving " << kTestCases[i].local_path; + } +} + +TEST(ExternalMountPointsTest, HandlesFileSystemMountType) { + scoped_refptr<storage::ExternalMountPoints> mount_points( + storage::ExternalMountPoints::CreateRefCounted()); + + const GURL test_origin("http://chromium.org"); + const base::FilePath test_path(FPL("/mount")); + + // Should handle External File System. + EXPECT_TRUE(mount_points->HandlesFileSystemMountType( + storage::kFileSystemTypeExternal)); + + // Shouldn't handle the rest. + EXPECT_FALSE(mount_points->HandlesFileSystemMountType( + storage::kFileSystemTypeIsolated)); + EXPECT_FALSE(mount_points->HandlesFileSystemMountType( + storage::kFileSystemTypeTemporary)); + EXPECT_FALSE(mount_points->HandlesFileSystemMountType( + storage::kFileSystemTypePersistent)); + EXPECT_FALSE( + mount_points->HandlesFileSystemMountType(storage::kFileSystemTypeTest)); + // Not even if it's external subtype. + EXPECT_FALSE(mount_points->HandlesFileSystemMountType( + storage::kFileSystemTypeNativeLocal)); + EXPECT_FALSE(mount_points->HandlesFileSystemMountType( + storage::kFileSystemTypeRestrictedNativeLocal)); + EXPECT_FALSE( + mount_points->HandlesFileSystemMountType(storage::kFileSystemTypeDrive)); + EXPECT_FALSE(mount_points->HandlesFileSystemMountType( + storage::kFileSystemTypeSyncable)); +} + +TEST(ExternalMountPointsTest, CreateCrackedFileSystemURL) { + scoped_refptr<storage::ExternalMountPoints> mount_points( + storage::ExternalMountPoints::CreateRefCounted()); + + const GURL kTestOrigin("http://chromium.org"); + + mount_points->RegisterFileSystem("c", + storage::kFileSystemTypeNativeLocal, + storage::FileSystemMountOption(), + base::FilePath(DRIVE FPL("/a/b/c"))); + mount_points->RegisterFileSystem("c(1)", + storage::kFileSystemTypeDrive, + storage::FileSystemMountOption(), + base::FilePath(DRIVE FPL("/a/b/c(1)"))); + mount_points->RegisterFileSystem("empty_path", + storage::kFileSystemTypeSyncable, + storage::FileSystemMountOption(), + base::FilePath()); + mount_points->RegisterFileSystem("mount", + storage::kFileSystemTypeDrive, + storage::FileSystemMountOption(), + base::FilePath(DRIVE FPL("/root"))); + + // Try cracking invalid GURL. + FileSystemURL invalid = mount_points->CrackURL(GURL("http://chromium.og")); + EXPECT_FALSE(invalid.is_valid()); + + // Try cracking isolated path. + FileSystemURL isolated = mount_points->CreateCrackedFileSystemURL( + kTestOrigin, storage::kFileSystemTypeIsolated, base::FilePath(FPL("c"))); + EXPECT_FALSE(isolated.is_valid()); + + // Try native local which is not cracked. + FileSystemURL native_local = mount_points->CreateCrackedFileSystemURL( + kTestOrigin, + storage::kFileSystemTypeNativeLocal, + base::FilePath(FPL("c"))); + EXPECT_FALSE(native_local.is_valid()); + + struct TestCase { + const base::FilePath::CharType* const path; + bool expect_valid; + storage::FileSystemType expect_type; + const base::FilePath::CharType* const expect_path; + const char* const expect_fs_id; + }; + + const TestCase kTestCases[] = { + {FPL("c/d/e"), true, storage::kFileSystemTypeNativeLocal, + DRIVE FPL("/a/b/c/d/e"), "c"}, + {FPL("c(1)/d/e"), true, storage::kFileSystemTypeDrive, + DRIVE FPL("/a/b/c(1)/d/e"), "c(1)"}, + {FPL("c(1)"), true, storage::kFileSystemTypeDrive, DRIVE FPL("/a/b/c(1)"), + "c(1)"}, + {FPL("empty_path/a"), true, storage::kFileSystemTypeSyncable, FPL("a"), + "empty_path"}, + {FPL("empty_path"), true, storage::kFileSystemTypeSyncable, FPL(""), + "empty_path"}, + {FPL("mount/a/b"), true, storage::kFileSystemTypeDrive, + DRIVE FPL("/root/a/b"), "mount"}, + {FPL("mount"), true, storage::kFileSystemTypeDrive, DRIVE FPL("/root"), + "mount"}, + {FPL("cc"), false, storage::kFileSystemTypeUnknown, FPL(""), ""}, + {FPL(""), false, storage::kFileSystemTypeUnknown, FPL(""), ""}, + {FPL(".."), false, storage::kFileSystemTypeUnknown, FPL(""), ""}, + // Absolte paths. + {FPL("/c/d/e"), false, storage::kFileSystemTypeUnknown, FPL(""), ""}, + {FPL("/c(1)/d/e"), false, storage::kFileSystemTypeUnknown, FPL(""), ""}, + {FPL("/empty_path"), false, storage::kFileSystemTypeUnknown, FPL(""), ""}, + // PAth references parent. + {FPL("c/d/../e"), false, storage::kFileSystemTypeUnknown, FPL(""), ""}, + {FPL("/empty_path/a/../b"), false, storage::kFileSystemTypeUnknown, FPL(""), + ""}, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + {FPL("c/d\\e"), true, storage::kFileSystemTypeNativeLocal, + DRIVE FPL("/a/b/c/d/e"), "c"}, + {FPL("mount\\a\\b"), true, storage::kFileSystemTypeDrive, + DRIVE FPL("/root/a/b"), "mount"}, +#endif + }; + + for (size_t i = 0; i < arraysize(kTestCases); ++i) { + FileSystemURL cracked = mount_points->CreateCrackedFileSystemURL( + kTestOrigin, + storage::kFileSystemTypeExternal, + base::FilePath(kTestCases[i].path)); + + EXPECT_EQ(kTestCases[i].expect_valid, cracked.is_valid()) + << "Test case index: " << i; + + if (!kTestCases[i].expect_valid) + continue; + + EXPECT_EQ(kTestOrigin, cracked.origin()) + << "Test case index: " << i; + EXPECT_EQ(kTestCases[i].expect_type, cracked.type()) + << "Test case index: " << i; + EXPECT_EQ(base::FilePath( + kTestCases[i].expect_path).NormalizePathSeparators(), cracked.path()) + << "Test case index: " << i; + EXPECT_EQ(base::FilePath(kTestCases[i].path).NormalizePathSeparators(), + cracked.virtual_path()) + << "Test case index: " << i; + EXPECT_EQ(kTestCases[i].expect_fs_id, cracked.filesystem_id()) + << "Test case index: " << i; + EXPECT_EQ(storage::kFileSystemTypeExternal, cracked.mount_type()) + << "Test case index: " << i; + } +} + +TEST(ExternalMountPointsTest, CrackVirtualPath) { + scoped_refptr<storage::ExternalMountPoints> mount_points( + storage::ExternalMountPoints::CreateRefCounted()); + + const GURL kTestOrigin("http://chromium.org"); + + mount_points->RegisterFileSystem("c", + storage::kFileSystemTypeNativeLocal, + storage::FileSystemMountOption(), + base::FilePath(DRIVE FPL("/a/b/c"))); + mount_points->RegisterFileSystem("c(1)", + storage::kFileSystemTypeDrive, + storage::FileSystemMountOption(), + base::FilePath(DRIVE FPL("/a/b/c(1)"))); + mount_points->RegisterFileSystem("empty_path", + storage::kFileSystemTypeSyncable, + storage::FileSystemMountOption(), + base::FilePath()); + mount_points->RegisterFileSystem("mount", + storage::kFileSystemTypeDrive, + storage::FileSystemMountOption(), + base::FilePath(DRIVE FPL("/root"))); + + struct TestCase { + const base::FilePath::CharType* const path; + bool expect_valid; + storage::FileSystemType expect_type; + const base::FilePath::CharType* const expect_path; + const char* const expect_name; + }; + + const TestCase kTestCases[] = { + {FPL("c/d/e"), true, storage::kFileSystemTypeNativeLocal, + DRIVE FPL("/a/b/c/d/e"), "c"}, + {FPL("c(1)/d/e"), true, storage::kFileSystemTypeDrive, + DRIVE FPL("/a/b/c(1)/d/e"), "c(1)"}, + {FPL("c(1)"), true, storage::kFileSystemTypeDrive, DRIVE FPL("/a/b/c(1)"), + "c(1)"}, + {FPL("empty_path/a"), true, storage::kFileSystemTypeSyncable, FPL("a"), + "empty_path"}, + {FPL("empty_path"), true, storage::kFileSystemTypeSyncable, FPL(""), + "empty_path"}, + {FPL("mount/a/b"), true, storage::kFileSystemTypeDrive, + DRIVE FPL("/root/a/b"), "mount"}, + {FPL("mount"), true, storage::kFileSystemTypeDrive, DRIVE FPL("/root"), + "mount"}, + {FPL("cc"), false, storage::kFileSystemTypeUnknown, FPL(""), ""}, + {FPL(""), false, storage::kFileSystemTypeUnknown, FPL(""), ""}, + {FPL(".."), false, storage::kFileSystemTypeUnknown, FPL(""), ""}, + // Absolte paths. + {FPL("/c/d/e"), false, storage::kFileSystemTypeUnknown, FPL(""), ""}, + {FPL("/c(1)/d/e"), false, storage::kFileSystemTypeUnknown, FPL(""), ""}, + {FPL("/empty_path"), false, storage::kFileSystemTypeUnknown, FPL(""), ""}, + // PAth references parent. + {FPL("c/d/../e"), false, storage::kFileSystemTypeUnknown, FPL(""), ""}, + {FPL("/empty_path/a/../b"), false, storage::kFileSystemTypeUnknown, FPL(""), + ""}, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + {FPL("c/d\\e"), true, storage::kFileSystemTypeNativeLocal, + DRIVE FPL("/a/b/c/d/e"), "c"}, + {FPL("mount\\a\\b"), true, storage::kFileSystemTypeDrive, + DRIVE FPL("/root/a/b"), "mount"}, +#endif + }; + + for (size_t i = 0; i < arraysize(kTestCases); ++i) { + std::string cracked_name; + storage::FileSystemType cracked_type; + std::string cracked_id; + base::FilePath cracked_path; + storage::FileSystemMountOption cracked_option; + EXPECT_EQ(kTestCases[i].expect_valid, + mount_points->CrackVirtualPath(base::FilePath(kTestCases[i].path), + &cracked_name, &cracked_type, &cracked_id, &cracked_path, + &cracked_option)) + << "Test case index: " << i; + + if (!kTestCases[i].expect_valid) + continue; + + EXPECT_EQ(kTestCases[i].expect_type, cracked_type) + << "Test case index: " << i; + EXPECT_EQ(base::FilePath( + kTestCases[i].expect_path).NormalizePathSeparators(), cracked_path) + << "Test case index: " << i; + EXPECT_EQ(kTestCases[i].expect_name, cracked_name) + << "Test case index: " << i; + // As of now we don't mount other filesystems with non-empty filesystem_id + // onto external mount points. + EXPECT_TRUE(cracked_id.empty()) << "Test case index: " << i; + } +} + +TEST(ExternalMountPointsTest, MountOption) { + scoped_refptr<storage::ExternalMountPoints> mount_points( + storage::ExternalMountPoints::CreateRefCounted()); + + mount_points->RegisterFileSystem( + "nosync", storage::kFileSystemTypeNativeLocal, + storage::FileSystemMountOption( + storage::FlushPolicy::NO_FLUSH_ON_COMPLETION), + base::FilePath(DRIVE FPL("/nosync"))); + mount_points->RegisterFileSystem( + "sync", storage::kFileSystemTypeNativeLocal, + storage::FileSystemMountOption(storage::FlushPolicy::FLUSH_ON_COMPLETION), + base::FilePath(DRIVE FPL("/sync"))); + + std::string name; + storage::FileSystemType type; + std::string cracked_id; + storage::FileSystemMountOption option; + base::FilePath path; + EXPECT_TRUE(mount_points->CrackVirtualPath( + base::FilePath(FPL("nosync/file")), &name, &type, &cracked_id, &path, + &option)); + EXPECT_EQ(storage::FlushPolicy::NO_FLUSH_ON_COMPLETION, + option.flush_policy()); + EXPECT_TRUE(mount_points->CrackVirtualPath( + base::FilePath(FPL("sync/file")), &name, &type, &cracked_id, &path, + &option)); + EXPECT_EQ(storage::FlushPolicy::FLUSH_ON_COMPLETION, option.flush_policy()); +} + +} // namespace content + diff --git a/chromium/storage/browser/fileapi/file_system_url_unittest.cc b/chromium/storage/browser/fileapi/file_system_url_unittest.cc new file mode 100644 index 00000000000..ef13cf103ca --- /dev/null +++ b/chromium/storage/browser/fileapi/file_system_url_unittest.cc @@ -0,0 +1,224 @@ +// Copyright 2014 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 "storage/browser/fileapi/file_system_url.h" + +#include <stddef.h> + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "storage/common/fileapi/file_system_types.h" +#include "storage/common/fileapi/file_system_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +#define FPL FILE_PATH_LITERAL + +#if defined(FILE_PATH_USES_DRIVE_LETTERS) +#define DRIVE FPL("C:") +#else +#define DRIVE FPL("/a/") +#endif + +using storage::FileSystemURL; +using storage::kFileSystemTypeExternal; +using storage::kFileSystemTypeIsolated; +using storage::kFileSystemTypePersistent; +using storage::kFileSystemTypeTemporary; +using storage::VirtualPath; + +namespace content { + +namespace { + +FileSystemURL CreateFileSystemURL(const std::string& url_string) { + FileSystemURL url = FileSystemURL::CreateForTest(GURL(url_string)); + EXPECT_TRUE(url.type() != kFileSystemTypeExternal && + url.type() != kFileSystemTypeIsolated); + return url; +} + +std::string NormalizedUTF8Path(const base::FilePath& path) { + return path.NormalizePathSeparators().AsUTF8Unsafe(); +} + +} // namespace + +TEST(FileSystemURLTest, ParsePersistent) { + FileSystemURL url = CreateFileSystemURL( + "filesystem:http://chromium.org/persistent/directory/file"); + ASSERT_TRUE(url.is_valid()); + EXPECT_EQ("http://chromium.org/", url.origin().spec()); + EXPECT_EQ(kFileSystemTypePersistent, url.type()); + EXPECT_EQ(FPL("file"), VirtualPath::BaseName(url.path()).value()); + EXPECT_EQ(FPL("directory"), url.path().DirName().value()); +} + +TEST(FileSystemURLTest, ParseTemporary) { + FileSystemURL url = CreateFileSystemURL( + "filesystem:http://chromium.org/temporary/directory/file"); + ASSERT_TRUE(url.is_valid()); + EXPECT_EQ("http://chromium.org/", url.origin().spec()); + EXPECT_EQ(kFileSystemTypeTemporary, url.type()); + EXPECT_EQ(FPL("file"), VirtualPath::BaseName(url.path()).value()); + EXPECT_EQ(FPL("directory"), url.path().DirName().value()); +} + +TEST(FileSystemURLTest, EnsureFilePathIsRelative) { + FileSystemURL url = CreateFileSystemURL( + "filesystem:http://chromium.org/temporary/////directory/file"); + ASSERT_TRUE(url.is_valid()); + EXPECT_EQ("http://chromium.org/", url.origin().spec()); + EXPECT_EQ(kFileSystemTypeTemporary, url.type()); + EXPECT_EQ(FPL("file"), VirtualPath::BaseName(url.path()).value()); + EXPECT_EQ(FPL("directory"), url.path().DirName().value()); + EXPECT_FALSE(url.path().IsAbsolute()); +} + +TEST(FileSystemURLTest, RejectBadSchemes) { + EXPECT_FALSE(CreateFileSystemURL("http://chromium.org/").is_valid()); + EXPECT_FALSE(CreateFileSystemURL("https://chromium.org/").is_valid()); + EXPECT_FALSE(CreateFileSystemURL("file:///foo/bar").is_valid()); + EXPECT_FALSE(CreateFileSystemURL("foobar:///foo/bar").is_valid()); +} + +TEST(FileSystemURLTest, UnescapePath) { + FileSystemURL url = CreateFileSystemURL( + "filesystem:http://chromium.org/persistent/%7Echromium/space%20bar"); + ASSERT_TRUE(url.is_valid()); + EXPECT_EQ(FPL("space bar"), VirtualPath::BaseName(url.path()).value()); + EXPECT_EQ(FPL("~chromium"), url.path().DirName().value()); +} + +TEST(FileSystemURLTest, RejectBadType) { + EXPECT_FALSE(CreateFileSystemURL( + "filesystem:http://c.org/foobar/file").is_valid()); + EXPECT_FALSE(CreateFileSystemURL( + "filesystem:http://c.org/temporaryfoo/file").is_valid()); +} + +TEST(FileSystemURLTest, RejectMalformedURL) { + EXPECT_FALSE(CreateFileSystemURL("filesystem:///foobar/file").is_valid()); + EXPECT_FALSE(CreateFileSystemURL("filesystem:foobar/file").is_valid()); +} + +TEST(FileSystemURLTest, CompareURLs) { + const GURL urls[] = { + GURL("filesystem:http://chromium.org/temporary/dir a/file a"), + GURL("filesystem:http://chromium.org/temporary/dir a/file a"), + GURL("filesystem:http://chromium.org/temporary/dir a/file b"), + GURL("filesystem:http://chromium.org/temporary/dir a/file aa"), + GURL("filesystem:http://chromium.org/temporary/dir b/file a"), + GURL("filesystem:http://chromium.org/temporary/dir aa/file b"), + GURL("filesystem:http://chromium.com/temporary/dir a/file a"), + GURL("filesystem:https://chromium.org/temporary/dir a/file a") + }; + + FileSystemURL::Comparator compare; + for (size_t i = 0; i < arraysize(urls); ++i) { + for (size_t j = 0; j < arraysize(urls); ++j) { + SCOPED_TRACE(testing::Message() << i << " < " << j); + EXPECT_EQ(urls[i] < urls[j], + compare(FileSystemURL::CreateForTest(urls[i]), + FileSystemURL::CreateForTest(urls[j]))); + } + } + + const FileSystemURL a = CreateFileSystemURL( + "filesystem:http://chromium.org/temporary/dir a/file a"); + const FileSystemURL b = CreateFileSystemURL( + "filesystem:http://chromium.org/persistent/dir a/file a"); + EXPECT_EQ(a.type() < b.type(), compare(a, b)); + EXPECT_EQ(b.type() < a.type(), compare(b, a)); +} + +TEST(FileSystemURLTest, IsParent) { + const std::string root1 = GetFileSystemRootURI( + GURL("http://example.com"), kFileSystemTypeTemporary).spec(); + const std::string root2 = GetFileSystemRootURI( + GURL("http://example.com"), kFileSystemTypePersistent).spec(); + const std::string root3 = GetFileSystemRootURI( + GURL("http://chromium.org"), kFileSystemTypeTemporary).spec(); + + const std::string parent("dir"); + const std::string child("dir/child"); + const std::string other("other"); + + // True cases. + EXPECT_TRUE(CreateFileSystemURL(root1 + parent).IsParent( + CreateFileSystemURL(root1 + child))); + EXPECT_TRUE(CreateFileSystemURL(root2 + parent).IsParent( + CreateFileSystemURL(root2 + child))); + + // False cases: the path is not a child. + EXPECT_FALSE(CreateFileSystemURL(root1 + parent).IsParent( + CreateFileSystemURL(root1 + other))); + EXPECT_FALSE(CreateFileSystemURL(root1 + parent).IsParent( + CreateFileSystemURL(root1 + parent))); + EXPECT_FALSE(CreateFileSystemURL(root1 + child).IsParent( + CreateFileSystemURL(root1 + parent))); + + // False case: different types. + EXPECT_FALSE(CreateFileSystemURL(root1 + parent).IsParent( + CreateFileSystemURL(root2 + child))); + + // False case: different origins. + EXPECT_FALSE(CreateFileSystemURL(root1 + parent).IsParent( + CreateFileSystemURL(root3 + child))); +} + +TEST(FileSystemURLTest, ToGURL) { + EXPECT_TRUE(FileSystemURL().ToGURL().is_empty()); + const char* kTestURL[] = { + "filesystem:http://chromium.org/persistent/directory/file0", + "filesystem:http://chromium.org/temporary/directory/file1", + "filesystem:http://chromium.org/isolated/directory/file2", + "filesystem:http://chromium.org/external/directory/file2", + "filesystem:http://chromium.org/test/directory/file3", + "filesystem:http://chromium.org/test/plus%2B/space%20/colon%3A", + }; + + for (size_t i = 0; i < arraysize(kTestURL); ++i) { + EXPECT_EQ( + kTestURL[i], + FileSystemURL::CreateForTest(GURL(kTestURL[i])).ToGURL().spec()); + } +} + +TEST(FileSystemURLTest, DebugString) { + const GURL kOrigin("http://example.com"); + const base::FilePath kPath(FPL("dir/file")); + + const FileSystemURL kURL1 = FileSystemURL::CreateForTest( + kOrigin, kFileSystemTypeTemporary, kPath); + EXPECT_EQ("filesystem:http://example.com/temporary/" + + NormalizedUTF8Path(kPath), + kURL1.DebugString()); +} + +TEST(FileSystemURLTest, IsInSameFileSystem) { + FileSystemURL url_foo_temp_a = FileSystemURL::CreateForTest( + GURL("http://foo"), kFileSystemTypeTemporary, + base::FilePath::FromUTF8Unsafe("a")); + FileSystemURL url_foo_temp_b = FileSystemURL::CreateForTest( + GURL("http://foo"), kFileSystemTypeTemporary, + base::FilePath::FromUTF8Unsafe("b")); + FileSystemURL url_foo_perm_a = FileSystemURL::CreateForTest( + GURL("http://foo"), kFileSystemTypePersistent, + base::FilePath::FromUTF8Unsafe("a")); + FileSystemURL url_bar_temp_a = FileSystemURL::CreateForTest( + GURL("http://bar"), kFileSystemTypeTemporary, + base::FilePath::FromUTF8Unsafe("a")); + FileSystemURL url_bar_perm_a = FileSystemURL::CreateForTest( + GURL("http://bar"), kFileSystemTypePersistent, + base::FilePath::FromUTF8Unsafe("a")); + + EXPECT_TRUE(url_foo_temp_a.IsInSameFileSystem(url_foo_temp_a)); + EXPECT_TRUE(url_foo_temp_a.IsInSameFileSystem(url_foo_temp_b)); + EXPECT_FALSE(url_foo_temp_a.IsInSameFileSystem(url_foo_perm_a)); + EXPECT_FALSE(url_foo_temp_a.IsInSameFileSystem(url_bar_temp_a)); + EXPECT_FALSE(url_foo_temp_a.IsInSameFileSystem(url_bar_perm_a)); +} + +} // namespace content diff --git a/chromium/storage/browser/fileapi/file_system_usage_cache_unittest.cc b/chromium/storage/browser/fileapi/file_system_usage_cache_unittest.cc new file mode 100644 index 00000000000..f6478b9e65a --- /dev/null +++ b/chromium/storage/browser/fileapi/file_system_usage_cache_unittest.cc @@ -0,0 +1,163 @@ +// Copyright 2014 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 "storage/browser/fileapi/file_system_usage_cache.h" + +#include <stdint.h> + +#include <limits> + +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "testing/gtest/include/gtest/gtest.h" + +using storage::FileSystemUsageCache; + +namespace content { + +class FileSystemUsageCacheTest : public testing::Test { + public: + FileSystemUsageCacheTest() + : usage_cache_(base::ThreadTaskRunnerHandle::Get().get()) {} + + void SetUp() override { ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); } + + protected: + base::FilePath GetUsageFilePath() { + return data_dir_.GetPath().Append(FileSystemUsageCache::kUsageFileName); + } + + FileSystemUsageCache* usage_cache() { + return &usage_cache_; + } + + private: + base::MessageLoop message_loop_; + base::ScopedTempDir data_dir_; + FileSystemUsageCache usage_cache_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemUsageCacheTest); +}; + +TEST_F(FileSystemUsageCacheTest, CreateTest) { + base::FilePath usage_file_path = GetUsageFilePath(); + EXPECT_TRUE(usage_cache()->UpdateUsage(usage_file_path, 0)); +} + +TEST_F(FileSystemUsageCacheTest, SetSizeTest) { + static const int64_t size = 240122; + base::FilePath usage_file_path = GetUsageFilePath(); + int64_t usage = 0; + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size)); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(size, usage); +} + +TEST_F(FileSystemUsageCacheTest, SetLargeSizeTest) { + static const int64_t size = std::numeric_limits<int64_t>::max(); + base::FilePath usage_file_path = GetUsageFilePath(); + int64_t usage = 0; + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size)); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(size, usage); +} + +TEST_F(FileSystemUsageCacheTest, IncAndGetSizeTest) { + base::FilePath usage_file_path = GetUsageFilePath(); + uint32_t dirty = 0; + int64_t usage = 0; + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, 98214)); + ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path)); + EXPECT_TRUE(usage_cache()->GetDirty(usage_file_path, &dirty)); + EXPECT_EQ(1u, dirty); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(98214, usage); +} + +TEST_F(FileSystemUsageCacheTest, DecAndGetSizeTest) { + static const int64_t size = 71839; + base::FilePath usage_file_path = GetUsageFilePath(); + int64_t usage = 0; + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size)); + // DecrementDirty for dirty = 0 is invalid. It returns false. + ASSERT_FALSE(usage_cache()->DecrementDirty(usage_file_path)); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(size, usage); +} + +TEST_F(FileSystemUsageCacheTest, IncDecAndGetSizeTest) { + static const int64_t size = 198491; + base::FilePath usage_file_path = GetUsageFilePath(); + int64_t usage = 0; + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size)); + ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path)); + ASSERT_TRUE(usage_cache()->DecrementDirty(usage_file_path)); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(size, usage); +} + +TEST_F(FileSystemUsageCacheTest, DecIncAndGetSizeTest) { + base::FilePath usage_file_path = GetUsageFilePath(); + uint32_t dirty = 0; + int64_t usage = 0; + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, 854238)); + // DecrementDirty for dirty = 0 is invalid. It returns false. + ASSERT_FALSE(usage_cache()->DecrementDirty(usage_file_path)); + ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path)); + // It tests DecrementDirty (which returns false) has no effect, i.e + // does not make dirty = -1 after DecrementDirty. + EXPECT_TRUE(usage_cache()->GetDirty(usage_file_path, &dirty)); + EXPECT_EQ(1u, dirty); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(854238, usage); +} + +TEST_F(FileSystemUsageCacheTest, ManyIncsSameDecsAndGetSizeTest) { + static const int64_t size = 82412; + base::FilePath usage_file_path = GetUsageFilePath(); + int64_t usage = 0; + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size)); + for (int i = 0; i < 20; i++) + ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path)); + for (int i = 0; i < 20; i++) + ASSERT_TRUE(usage_cache()->DecrementDirty(usage_file_path)); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(size, usage); +} + +TEST_F(FileSystemUsageCacheTest, ManyIncsLessDecsAndGetSizeTest) { + uint32_t dirty = 0; + int64_t usage = 0; + base::FilePath usage_file_path = GetUsageFilePath(); + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, 19319)); + for (int i = 0; i < 20; i++) + ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path)); + for (int i = 0; i < 19; i++) + ASSERT_TRUE(usage_cache()->DecrementDirty(usage_file_path)); + EXPECT_TRUE(usage_cache()->GetDirty(usage_file_path, &dirty)); + EXPECT_EQ(1u, dirty); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(19319, usage); +} + +TEST_F(FileSystemUsageCacheTest, GetSizeWithoutCacheFileTest) { + int64_t usage = 0; + base::FilePath usage_file_path = GetUsageFilePath(); + EXPECT_FALSE(usage_cache()->GetUsage(usage_file_path, &usage)); +} + +TEST_F(FileSystemUsageCacheTest, IncrementDirtyWithoutCacheFileTest) { + base::FilePath usage_file_path = GetUsageFilePath(); + EXPECT_FALSE(usage_cache()->IncrementDirty(usage_file_path)); +} + +TEST_F(FileSystemUsageCacheTest, DecrementDirtyWithoutCacheFileTest) { + base::FilePath usage_file_path = GetUsageFilePath(); + EXPECT_FALSE(usage_cache()->IncrementDirty(usage_file_path)); +} + +} // namespace content diff --git a/chromium/storage/browser/fileapi/isolated_context.h b/chromium/storage/browser/fileapi/isolated_context.h index 1f1c1d91f00..713b1056be2 100644 --- a/chromium/storage/browser/fileapi/isolated_context.h +++ b/chromium/storage/browser/fileapi/isolated_context.h @@ -161,7 +161,7 @@ class STORAGE_EXPORT IsolatedContext : public MountPoints { base::FilePath CreateVirtualRootPath(const std::string& filesystem_id) const; private: - friend struct base::DefaultLazyInstanceTraits<IsolatedContext>; + friend struct base::LazyInstanceTraitsBase<IsolatedContext>; // Represents each file system instance (defined in the .cc). class Instance; diff --git a/chromium/storage/browser/fileapi/isolated_context_unittest.cc b/chromium/storage/browser/fileapi/isolated_context_unittest.cc new file mode 100644 index 00000000000..64f0c29404c --- /dev/null +++ b/chromium/storage/browser/fileapi/isolated_context_unittest.cc @@ -0,0 +1,363 @@ +// Copyright 2014 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 <stddef.h> + +#include <string> + +#include "base/logging.h" +#include "base/macros.h" +#include "storage/browser/fileapi/file_system_url.h" +#include "storage/browser/fileapi/isolated_context.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define FPL(x) FILE_PATH_LITERAL(x) + +#if defined(FILE_PATH_USES_DRIVE_LETTERS) +#define DRIVE FPL("C:") +#else +#define DRIVE +#endif + +using storage::FileSystemMountOption; +using storage::FileSystemURL; +using storage::IsolatedContext; +using storage::kFileSystemTypeDragged; +using storage::kFileSystemTypeIsolated; +using storage::kFileSystemTypeNativeLocal; + +namespace content { + +typedef IsolatedContext::MountPointInfo FileInfo; + +namespace { + +const base::FilePath kTestPaths[] = { + base::FilePath(DRIVE FPL("/a/b.txt")), + base::FilePath(DRIVE FPL("/c/d/e")), + base::FilePath(DRIVE FPL("/h/")), + base::FilePath(DRIVE FPL("/")), +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + base::FilePath(DRIVE FPL("\\foo\\bar")), + base::FilePath(DRIVE FPL("\\")), +#endif + // For duplicated base name test. + base::FilePath(DRIVE FPL("/")), + base::FilePath(DRIVE FPL("/f/e")), + base::FilePath(DRIVE FPL("/f/b.txt")), +}; + +} // namespace + +class IsolatedContextTest : public testing::Test { + public: + IsolatedContextTest() { + for (size_t i = 0; i < arraysize(kTestPaths); ++i) + fileset_.insert(kTestPaths[i].NormalizePathSeparators()); + } + + void SetUp() override { + IsolatedContext::FileInfoSet files; + for (size_t i = 0; i < arraysize(kTestPaths); ++i) { + std::string name; + ASSERT_TRUE( + files.AddPath(kTestPaths[i].NormalizePathSeparators(), &name)); + names_.push_back(name); + } + id_ = IsolatedContext::GetInstance()->RegisterDraggedFileSystem(files); + IsolatedContext::GetInstance()->AddReference(id_); + ASSERT_FALSE(id_.empty()); + } + + void TearDown() override { + IsolatedContext::GetInstance()->RemoveReference(id_); + } + + IsolatedContext* isolated_context() const { + return IsolatedContext::GetInstance(); + } + + protected: + std::string id_; + std::multiset<base::FilePath> fileset_; + std::vector<std::string> names_; + + private: + DISALLOW_COPY_AND_ASSIGN(IsolatedContextTest); +}; + +TEST_F(IsolatedContextTest, RegisterAndRevokeTest) { + // See if the returned top-level entries match with what we registered. + std::vector<FileInfo> toplevels; + ASSERT_TRUE(isolated_context()->GetDraggedFileInfo(id_, &toplevels)); + ASSERT_EQ(fileset_.size(), toplevels.size()); + for (size_t i = 0; i < toplevels.size(); ++i) { + ASSERT_TRUE(fileset_.find(toplevels[i].path) != fileset_.end()); + } + + // See if the name of each registered kTestPaths (that is what we + // register in SetUp() by RegisterDraggedFileSystem) is properly cracked as + // a valid virtual path in the isolated filesystem. + for (size_t i = 0; i < arraysize(kTestPaths); ++i) { + base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath(id_) + .AppendASCII(names_[i]); + std::string cracked_id; + base::FilePath cracked_path; + std::string cracked_inner_id; + storage::FileSystemType cracked_type; + FileSystemMountOption cracked_option; + ASSERT_TRUE(isolated_context()->CrackVirtualPath( + virtual_path, &cracked_id, &cracked_type, &cracked_inner_id, + &cracked_path, &cracked_option)); + ASSERT_EQ(kTestPaths[i].NormalizePathSeparators().value(), + cracked_path.value()); + ASSERT_EQ(id_, cracked_id); + ASSERT_EQ(kFileSystemTypeDragged, cracked_type); + EXPECT_TRUE(cracked_inner_id.empty()); + } + + // Make sure GetRegisteredPath returns false for id_ since it is + // registered for dragged files. + base::FilePath path; + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id_, &path)); + + // Deref the current one and registering a new one. + isolated_context()->RemoveReference(id_); + + std::string id2 = isolated_context()->RegisterFileSystemForPath( + kFileSystemTypeNativeLocal, std::string(), + base::FilePath(DRIVE FPL("/foo")), NULL); + + // Make sure the GetDraggedFileInfo returns false for both ones. + ASSERT_FALSE(isolated_context()->GetDraggedFileInfo(id2, &toplevels)); + ASSERT_FALSE(isolated_context()->GetDraggedFileInfo(id_, &toplevels)); + + // Make sure the GetRegisteredPath returns true only for the new one. + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id_, &path)); + ASSERT_TRUE(isolated_context()->GetRegisteredPath(id2, &path)); + + // Try registering three more file systems for the same path as id2. + std::string id3 = isolated_context()->RegisterFileSystemForPath( + kFileSystemTypeNativeLocal, std::string(), path, NULL); + std::string id4 = isolated_context()->RegisterFileSystemForPath( + kFileSystemTypeNativeLocal, std::string(), path, NULL); + std::string id5 = isolated_context()->RegisterFileSystemForPath( + kFileSystemTypeNativeLocal, std::string(), path, NULL); + + // Remove file system for id4. + isolated_context()->AddReference(id4); + isolated_context()->RemoveReference(id4); + + // Only id4 should become invalid now. + ASSERT_TRUE(isolated_context()->GetRegisteredPath(id2, &path)); + ASSERT_TRUE(isolated_context()->GetRegisteredPath(id3, &path)); + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id4, &path)); + ASSERT_TRUE(isolated_context()->GetRegisteredPath(id5, &path)); + + // Revoke file system id5, after adding multiple references. + isolated_context()->AddReference(id5); + isolated_context()->AddReference(id5); + isolated_context()->AddReference(id5); + isolated_context()->RevokeFileSystem(id5); + + // No matter how many references we add id5 must be invalid now. + ASSERT_TRUE(isolated_context()->GetRegisteredPath(id2, &path)); + ASSERT_TRUE(isolated_context()->GetRegisteredPath(id3, &path)); + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id4, &path)); + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id5, &path)); + + // Revoke the file systems by path. + isolated_context()->RevokeFileSystemByPath(path); + + // Now all the file systems associated to the path must be invalid. + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id2, &path)); + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id3, &path)); + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id4, &path)); + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id5, &path)); +} + +TEST_F(IsolatedContextTest, CrackWithRelativePaths) { + const struct { + base::FilePath::StringType path; + bool valid; + } relatives[] = { + { FPL("foo"), true }, + { FPL("foo/bar"), true }, + { FPL(".."), false }, + { FPL("foo/.."), false }, + { FPL("foo/../bar"), false }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) +# define SHOULD_FAIL_WITH_WIN_SEPARATORS false +#else +# define SHOULD_FAIL_WITH_WIN_SEPARATORS true +#endif + { FPL("foo\\..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS }, + { FPL("foo/..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS }, + }; + + for (size_t i = 0; i < arraysize(kTestPaths); ++i) { + for (size_t j = 0; j < arraysize(relatives); ++j) { + SCOPED_TRACE(testing::Message() << "Testing " + << kTestPaths[i].value() << " " << relatives[j].path); + base::FilePath virtual_path = + isolated_context()->CreateVirtualRootPath(id_).AppendASCII( + names_[i]).Append(relatives[j].path); + std::string cracked_id; + base::FilePath cracked_path; + storage::FileSystemType cracked_type; + std::string cracked_inner_id; + FileSystemMountOption cracked_option; + if (!relatives[j].valid) { + ASSERT_FALSE(isolated_context()->CrackVirtualPath( + virtual_path, &cracked_id, &cracked_type, &cracked_inner_id, + &cracked_path, &cracked_option)); + continue; + } + ASSERT_TRUE(isolated_context()->CrackVirtualPath( + virtual_path, &cracked_id, &cracked_type, &cracked_inner_id, + &cracked_path, &cracked_option)); + ASSERT_EQ(kTestPaths[i].Append(relatives[j].path) + .NormalizePathSeparators().value(), + cracked_path.value()); + ASSERT_EQ(id_, cracked_id); + ASSERT_EQ(kFileSystemTypeDragged, cracked_type); + EXPECT_TRUE(cracked_inner_id.empty()); + } + } +} + +TEST_F(IsolatedContextTest, CrackURLWithRelativePaths) { + const struct { + base::FilePath::StringType path; + bool valid; + } relatives[] = { + { FPL("foo"), true }, + { FPL("foo/bar"), true }, + { FPL(".."), false }, + { FPL("foo/.."), false }, + { FPL("foo/../bar"), false }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) +# define SHOULD_FAIL_WITH_WIN_SEPARATORS false +#else +# define SHOULD_FAIL_WITH_WIN_SEPARATORS true +#endif + { FPL("foo\\..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS }, + { FPL("foo/..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS }, + }; + + for (size_t i = 0; i < arraysize(kTestPaths); ++i) { + for (size_t j = 0; j < arraysize(relatives); ++j) { + SCOPED_TRACE(testing::Message() << "Testing " + << kTestPaths[i].value() << " " << relatives[j].path); + base::FilePath virtual_path = + isolated_context()->CreateVirtualRootPath(id_).AppendASCII( + names_[i]).Append(relatives[j].path); + + FileSystemURL cracked = isolated_context()->CreateCrackedFileSystemURL( + GURL("http://chromium.org"), kFileSystemTypeIsolated, virtual_path); + + ASSERT_EQ(relatives[j].valid, cracked.is_valid()); + + if (!relatives[j].valid) + continue; + ASSERT_EQ(GURL("http://chromium.org"), cracked.origin()); + ASSERT_EQ(kTestPaths[i].Append(relatives[j].path) + .NormalizePathSeparators().value(), + cracked.path().value()); + ASSERT_EQ(virtual_path.NormalizePathSeparators(), cracked.virtual_path()); + ASSERT_EQ(id_, cracked.filesystem_id()); + ASSERT_EQ(kFileSystemTypeDragged, cracked.type()); + ASSERT_EQ(kFileSystemTypeIsolated, cracked.mount_type()); + } + } +} + +TEST_F(IsolatedContextTest, TestWithVirtualRoot) { + std::string cracked_id; + base::FilePath cracked_path; + FileSystemMountOption cracked_option; + + // Trying to crack virtual root "/" returns true but with empty cracked path + // as "/" of the isolated filesystem is a pure virtual directory + // that has no corresponding platform directory. + base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath(id_); + ASSERT_TRUE(isolated_context()->CrackVirtualPath( + virtual_path, &cracked_id, NULL, NULL, &cracked_path, &cracked_option)); + ASSERT_EQ(FPL(""), cracked_path.value()); + ASSERT_EQ(id_, cracked_id); + + // Trying to crack "/foo" should fail (because "foo" is not the one + // included in the kTestPaths). + virtual_path = isolated_context()->CreateVirtualRootPath( + id_).AppendASCII("foo"); + ASSERT_FALSE(isolated_context()->CrackVirtualPath( + virtual_path, &cracked_id, NULL, NULL, &cracked_path, &cracked_option)); +} + +TEST_F(IsolatedContextTest, CanHandleURL) { + const GURL test_origin("http://chromium.org"); + const base::FilePath test_path(FPL("/mount")); + + // Should handle isolated file system. + EXPECT_TRUE(isolated_context()->HandlesFileSystemMountType( + storage::kFileSystemTypeIsolated)); + + // Shouldn't handle the rest. + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + storage::kFileSystemTypeExternal)); + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + storage::kFileSystemTypeTemporary)); + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + storage::kFileSystemTypePersistent)); + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + storage::kFileSystemTypeTest)); + // Not even if it's isolated subtype. + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + storage::kFileSystemTypeNativeLocal)); + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + storage::kFileSystemTypeDragged)); + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + storage::kFileSystemTypeNativeMedia)); + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + storage::kFileSystemTypeDeviceMedia)); +} + +TEST_F(IsolatedContextTest, VirtualFileSystemTests) { + // Should be able to register empty and non-absolute paths + std::string empty_fsid = isolated_context()->RegisterFileSystemForVirtualPath( + storage::kFileSystemTypeIsolated, "_", base::FilePath()); + std::string relative_fsid = + isolated_context()->RegisterFileSystemForVirtualPath( + storage::kFileSystemTypeIsolated, + "_", + base::FilePath(FPL("relpath"))); + ASSERT_FALSE(empty_fsid.empty()); + ASSERT_FALSE(relative_fsid.empty()); + + // Make sure that filesystem root is not prepended to cracked virtual paths. + base::FilePath database_root = base::FilePath(DRIVE FPL("/database_path")); + std::string database_fsid = + isolated_context()->RegisterFileSystemForVirtualPath( + storage::kFileSystemTypeIsolated, "_", database_root); + + base::FilePath test_virtual_path = + base::FilePath().AppendASCII("virtualdir").AppendASCII("virtualfile.txt"); + + base::FilePath whole_virtual_path = + isolated_context()->CreateVirtualRootPath(database_fsid) + .AppendASCII("_").Append(test_virtual_path); + + std::string cracked_id; + base::FilePath cracked_path; + std::string cracked_inner_id; + FileSystemMountOption cracked_option; + ASSERT_TRUE(isolated_context()->CrackVirtualPath( + whole_virtual_path, &cracked_id, NULL, &cracked_inner_id, + &cracked_path, &cracked_option)); + ASSERT_EQ(database_fsid, cracked_id); + ASSERT_EQ(test_virtual_path, cracked_path); + EXPECT_TRUE(cracked_inner_id.empty()); +} + +} // namespace content diff --git a/chromium/storage/browser/fileapi/local_file_stream_reader_unittest.cc b/chromium/storage/browser/fileapi/local_file_stream_reader_unittest.cc new file mode 100644 index 00000000000..d0c7c72007d --- /dev/null +++ b/chromium/storage/browser/fileapi/local_file_stream_reader_unittest.cc @@ -0,0 +1,283 @@ +// Copyright 2014 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 "storage/browser/fileapi/local_file_stream_reader.h" + +#include <stddef.h> +#include <stdint.h> + +#include <memory> +#include <string> + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "testing/gtest/include/gtest/gtest.h" + +using storage::LocalFileStreamReader; + +namespace content { + +namespace { + +const char kTestData[] = "0123456789"; +const int kTestDataSize = arraysize(kTestData) - 1; + +void ReadFromReader(LocalFileStreamReader* reader, + std::string* data, size_t size, + int* result) { + ASSERT_TRUE(reader != NULL); + ASSERT_TRUE(result != NULL); + *result = net::OK; + net::TestCompletionCallback callback; + size_t total_bytes_read = 0; + while (total_bytes_read < size) { + scoped_refptr<net::IOBufferWithSize> buf( + new net::IOBufferWithSize(size - total_bytes_read)); + int rv = reader->Read(buf.get(), buf->size(), callback.callback()); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + if (rv < 0) + *result = rv; + if (rv <= 0) + break; + total_bytes_read += rv; + data->append(buf->data(), rv); + } +} + +void NeverCalled(int) { ADD_FAILURE(); } +void EmptyCallback() {} + +void QuitLoop() { + base::MessageLoop::current()->QuitWhenIdle(); +} + +} // namespace + +class LocalFileStreamReaderTest : public testing::Test { + public: + LocalFileStreamReaderTest() + : file_thread_("FileUtilProxyTestFileThread") {} + + void SetUp() override { + ASSERT_TRUE(file_thread_.Start()); + ASSERT_TRUE(dir_.CreateUniqueTempDir()); + + base::WriteFile(test_path(), kTestData, kTestDataSize); + base::File::Info info; + ASSERT_TRUE(base::GetFileInfo(test_path(), &info)); + test_file_modification_time_ = info.last_modified; + } + + void TearDown() override { + // Give another chance for deleted streams to perform Close. + base::RunLoop().RunUntilIdle(); + file_thread_.Stop(); + base::RunLoop().RunUntilIdle(); + } + + protected: + LocalFileStreamReader* CreateFileReader( + const base::FilePath& path, + int64_t initial_offset, + const base::Time& expected_modification_time) { + return new LocalFileStreamReader( + file_task_runner(), + path, + initial_offset, + expected_modification_time); + } + + void TouchTestFile(base::TimeDelta delta) { + base::Time new_modified_time = test_file_modification_time() + delta; + ASSERT_TRUE(base::TouchFile(test_path(), + test_file_modification_time(), + new_modified_time)); + } + + base::SingleThreadTaskRunner* file_task_runner() const { + return file_thread_.task_runner().get(); + } + + base::FilePath test_dir() const { return dir_.GetPath(); } + base::FilePath test_path() const { + return dir_.GetPath().AppendASCII("test"); + } + base::Time test_file_modification_time() const { + return test_file_modification_time_; + } + + void EnsureFileTaskFinished() { + file_task_runner()->PostTaskAndReply( + FROM_HERE, base::Bind(&EmptyCallback), base::Bind(&QuitLoop)); + base::RunLoop().Run(); + } + + private: + base::MessageLoopForIO message_loop_; + base::Thread file_thread_; + base::ScopedTempDir dir_; + base::Time test_file_modification_time_; +}; + +TEST_F(LocalFileStreamReaderTest, NonExistent) { + base::FilePath nonexistent_path = test_dir().AppendASCII("nonexistent"); + std::unique_ptr<LocalFileStreamReader> reader( + CreateFileReader(nonexistent_path, 0, base::Time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, 10, &result); + ASSERT_EQ(net::ERR_FILE_NOT_FOUND, result); + ASSERT_EQ(0U, data.size()); +} + +TEST_F(LocalFileStreamReaderTest, Empty) { + base::FilePath empty_path = test_dir().AppendASCII("empty"); + base::File file(empty_path, base::File::FLAG_CREATE | base::File::FLAG_READ); + ASSERT_TRUE(file.IsValid()); + file.Close(); + + std::unique_ptr<LocalFileStreamReader> reader( + CreateFileReader(empty_path, 0, base::Time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, 10, &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(0U, data.size()); + + net::TestInt64CompletionCallback callback; + int64_t length_result = reader->GetLength(callback.callback()); + if (length_result == net::ERR_IO_PENDING) + length_result = callback.WaitForResult(); + ASSERT_EQ(0, result); +} + +TEST_F(LocalFileStreamReaderTest, GetLengthNormal) { + std::unique_ptr<LocalFileStreamReader> reader( + CreateFileReader(test_path(), 0, test_file_modification_time())); + net::TestInt64CompletionCallback callback; + int64_t result = reader->GetLength(callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + ASSERT_EQ(kTestDataSize, result); +} + +TEST_F(LocalFileStreamReaderTest, GetLengthAfterModified) { + // Touch file so that the file's modification time becomes different + // from what we expect. + TouchTestFile(base::TimeDelta::FromSeconds(-1)); + + std::unique_ptr<LocalFileStreamReader> reader( + CreateFileReader(test_path(), 0, test_file_modification_time())); + net::TestInt64CompletionCallback callback; + int64_t result = reader->GetLength(callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); + + // With NULL expected modification time this should work. + reader.reset(CreateFileReader(test_path(), 0, base::Time())); + result = reader->GetLength(callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + ASSERT_EQ(kTestDataSize, result); +} + +TEST_F(LocalFileStreamReaderTest, GetLengthWithOffset) { + std::unique_ptr<LocalFileStreamReader> reader( + CreateFileReader(test_path(), 3, base::Time())); + net::TestInt64CompletionCallback callback; + int64_t result = reader->GetLength(callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + // Initial offset does not affect the result of GetLength. + ASSERT_EQ(kTestDataSize, result); +} + +TEST_F(LocalFileStreamReaderTest, ReadNormal) { + std::unique_ptr<LocalFileStreamReader> reader( + CreateFileReader(test_path(), 0, test_file_modification_time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, kTestDataSize, &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(kTestData, data); +} + +TEST_F(LocalFileStreamReaderTest, ReadAfterModified) { + // Touch file so that the file's modification time becomes different + // from what we expect. Note that the resolution on some filesystems + // is 1s so we can't test with deltas less than that. + TouchTestFile(base::TimeDelta::FromSeconds(-1)); + std::unique_ptr<LocalFileStreamReader> reader( + CreateFileReader(test_path(), 0, test_file_modification_time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, kTestDataSize, &result); + EXPECT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); + EXPECT_EQ(0U, data.size()); + + // Due to precision loss converting int64_t->double->int64_t (e.g. through + // Blink) the expected/actual time may vary by microseconds. With + // modification time delta < 10us this should work. + TouchTestFile(base::TimeDelta::FromMicroseconds(1)); + data.clear(); + reader.reset(CreateFileReader(test_path(), 0, test_file_modification_time())); + ReadFromReader(reader.get(), &data, kTestDataSize, &result); + EXPECT_EQ(net::OK, result); + EXPECT_EQ(kTestData, data); + + // With matching modification times time this should work. + TouchTestFile(base::TimeDelta()); + data.clear(); + reader.reset(CreateFileReader(test_path(), 0, test_file_modification_time())); + ReadFromReader(reader.get(), &data, kTestDataSize, &result); + EXPECT_EQ(net::OK, result); + EXPECT_EQ(kTestData, data); + + // And with NULL expected modification time this should work. + data.clear(); + reader.reset(CreateFileReader(test_path(), 0, base::Time())); + ReadFromReader(reader.get(), &data, kTestDataSize, &result); + EXPECT_EQ(net::OK, result); + EXPECT_EQ(kTestData, data); +} + +TEST_F(LocalFileStreamReaderTest, ReadWithOffset) { + std::unique_ptr<LocalFileStreamReader> reader( + CreateFileReader(test_path(), 3, base::Time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, kTestDataSize, &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(&kTestData[3], data); +} + +TEST_F(LocalFileStreamReaderTest, DeleteWithUnfinishedRead) { + std::unique_ptr<LocalFileStreamReader> reader( + CreateFileReader(test_path(), 0, base::Time())); + + net::TestCompletionCallback callback; + scoped_refptr<net::IOBufferWithSize> buf( + new net::IOBufferWithSize(kTestDataSize)); + int rv = reader->Read(buf.get(), buf->size(), base::Bind(&NeverCalled)); + ASSERT_TRUE(rv == net::ERR_IO_PENDING || rv >= 0); + + // Delete immediately. + // Should not crash; nor should NeverCalled be callback. + reader.reset(); + EnsureFileTaskFinished(); +} + +} // namespace content diff --git a/chromium/storage/browser/fileapi/local_file_stream_writer_unittest.cc b/chromium/storage/browser/fileapi/local_file_stream_writer_unittest.cc new file mode 100644 index 00000000000..ca798469091 --- /dev/null +++ b/chromium/storage/browser/fileapi/local_file_stream_writer_unittest.cc @@ -0,0 +1,182 @@ +// Copyright 2014 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 "storage/browser/fileapi/local_file_stream_writer.h" + +#include <stdint.h> + +#include <memory> +#include <string> + +#include "base/callback.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread.h" +#include "net/base/io_buffer.h" +#include "net/base/test_completion_callback.h" +#include "testing/gtest/include/gtest/gtest.h" + +using storage::FileStreamWriter; +using storage::LocalFileStreamWriter; + +namespace content { + +class LocalFileStreamWriterTest : public testing::Test { + public: + LocalFileStreamWriterTest() + : file_thread_("FileUtilProxyTestFileThread") {} + + void SetUp() override { + ASSERT_TRUE(file_thread_.Start()); + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + } + + void TearDown() override { + // Give another chance for deleted streams to perform Close. + base::RunLoop().RunUntilIdle(); + file_thread_.Stop(); + base::RunLoop().RunUntilIdle(); + } + + protected: + base::FilePath Path(const std::string& name) { + return temp_dir_.GetPath().AppendASCII(name); + } + + int WriteStringToWriter(LocalFileStreamWriter* writer, + const std::string& data) { + scoped_refptr<net::StringIOBuffer> buffer(new net::StringIOBuffer(data)); + scoped_refptr<net::DrainableIOBuffer> drainable( + new net::DrainableIOBuffer(buffer.get(), buffer->size())); + + while (drainable->BytesRemaining() > 0) { + net::TestCompletionCallback callback; + int result = writer->Write( + drainable.get(), drainable->BytesRemaining(), callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + if (result <= 0) + return result; + drainable->DidConsume(result); + } + return net::OK; + } + + std::string GetFileContent(const base::FilePath& path) { + std::string content; + base::ReadFileToString(path, &content); + return content; + } + + base::FilePath CreateFileWithContent(const std::string& name, + const std::string& data) { + base::FilePath path = Path(name); + base::WriteFile(path, data.c_str(), data.size()); + return path; + } + + base::SingleThreadTaskRunner* file_task_runner() const { + return file_thread_.task_runner().get(); + } + + LocalFileStreamWriter* CreateWriter(const base::FilePath& path, + int64_t offset) { + return new LocalFileStreamWriter(file_task_runner(), path, offset, + FileStreamWriter::OPEN_EXISTING_FILE); + } + + private: + base::MessageLoopForIO message_loop_; + base::Thread file_thread_; + base::ScopedTempDir temp_dir_; +}; + +void NeverCalled(int unused) { + ADD_FAILURE(); +} + +TEST_F(LocalFileStreamWriterTest, Write) { + base::FilePath path = CreateFileWithContent("file_a", std::string()); + std::unique_ptr<LocalFileStreamWriter> writer(CreateWriter(path, 0)); + EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "foo")); + EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "bar")); + writer.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(base::PathExists(path)); + EXPECT_EQ("foobar", GetFileContent(path)); +} + +TEST_F(LocalFileStreamWriterTest, WriteMiddle) { + base::FilePath path = CreateFileWithContent("file_a", "foobar"); + std::unique_ptr<LocalFileStreamWriter> writer(CreateWriter(path, 2)); + EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "xxx")); + writer.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(base::PathExists(path)); + EXPECT_EQ("foxxxr", GetFileContent(path)); +} + +TEST_F(LocalFileStreamWriterTest, WriteEnd) { + base::FilePath path = CreateFileWithContent("file_a", "foobar"); + std::unique_ptr<LocalFileStreamWriter> writer(CreateWriter(path, 6)); + EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "xxx")); + writer.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(base::PathExists(path)); + EXPECT_EQ("foobarxxx", GetFileContent(path)); +} + +TEST_F(LocalFileStreamWriterTest, WriteFailForNonexistingFile) { + base::FilePath path = Path("file_a"); + ASSERT_FALSE(base::PathExists(path)); + std::unique_ptr<LocalFileStreamWriter> writer(CreateWriter(path, 0)); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, WriteStringToWriter(writer.get(), "foo")); + writer.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(base::PathExists(path)); +} + +TEST_F(LocalFileStreamWriterTest, CancelBeforeOperation) { + base::FilePath path = Path("file_a"); + std::unique_ptr<LocalFileStreamWriter> writer(CreateWriter(path, 0)); + // Cancel immediately fails when there's no in-flight operation. + int cancel_result = writer->Cancel(base::Bind(&NeverCalled)); + EXPECT_EQ(net::ERR_UNEXPECTED, cancel_result); +} + +TEST_F(LocalFileStreamWriterTest, CancelAfterFinishedOperation) { + base::FilePath path = CreateFileWithContent("file_a", std::string()); + std::unique_ptr<LocalFileStreamWriter> writer(CreateWriter(path, 0)); + EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "foo")); + + // Cancel immediately fails when there's no in-flight operation. + int cancel_result = writer->Cancel(base::Bind(&NeverCalled)); + EXPECT_EQ(net::ERR_UNEXPECTED, cancel_result); + + writer.reset(); + base::RunLoop().RunUntilIdle(); + // Write operation is already completed. + EXPECT_TRUE(base::PathExists(path)); + EXPECT_EQ("foo", GetFileContent(path)); +} + +TEST_F(LocalFileStreamWriterTest, CancelWrite) { + base::FilePath path = CreateFileWithContent("file_a", "foobar"); + std::unique_ptr<LocalFileStreamWriter> writer(CreateWriter(path, 0)); + + scoped_refptr<net::StringIOBuffer> buffer(new net::StringIOBuffer("xxx")); + int result = + writer->Write(buffer.get(), buffer->size(), base::Bind(&NeverCalled)); + ASSERT_EQ(net::ERR_IO_PENDING, result); + + net::TestCompletionCallback callback; + writer->Cancel(callback.callback()); + int cancel_result = callback.WaitForResult(); + EXPECT_EQ(net::OK, cancel_result); +} + +} // namespace content diff --git a/chromium/storage/browser/fileapi/native_file_util_unittest.cc b/chromium/storage/browser/fileapi/native_file_util_unittest.cc new file mode 100644 index 00000000000..49969e380b5 --- /dev/null +++ b/chromium/storage/browser/fileapi/native_file_util_unittest.cc @@ -0,0 +1,409 @@ +// Copyright 2014 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 <stdint.h> + +#include <memory> +#include <set> + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/macros.h" +#include "storage/browser/fileapi/native_file_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +using storage::FileSystemFileUtil; +using storage::FileSystemOperation; +using storage::NativeFileUtil; + +namespace content { + +class NativeFileUtilTest : public testing::Test { + public: + NativeFileUtilTest() {} + + void SetUp() override { ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); } + + protected: + base::FilePath Path() { return data_dir_.GetPath(); } + + base::FilePath Path(const char* file_name) { + return data_dir_.GetPath().AppendASCII(file_name); + } + + bool FileExists(const base::FilePath& path) { + return base::PathExists(path) && + !base::DirectoryExists(path); + } + + int64_t GetSize(const base::FilePath& path) { + base::File::Info info; + base::GetFileInfo(path, &info); + return info.size; + } + + private: + base::ScopedTempDir data_dir_; + + DISALLOW_COPY_AND_ASSIGN(NativeFileUtilTest); +}; + +TEST_F(NativeFileUtilTest, CreateCloseAndDeleteFile) { + base::FilePath file_name = Path("test_file"); + int flags = base::File::FLAG_WRITE | base::File::FLAG_ASYNC; + base::File file = + NativeFileUtil::CreateOrOpen(file_name, base::File::FLAG_CREATE | flags); + ASSERT_TRUE(file.IsValid()); + ASSERT_TRUE(file.created()); + + EXPECT_TRUE(base::PathExists(file_name)); + EXPECT_TRUE(NativeFileUtil::PathExists(file_name)); + EXPECT_EQ(0, GetSize(file_name)); + file.Close(); + + file = NativeFileUtil::CreateOrOpen(file_name, base::File::FLAG_OPEN | flags); + ASSERT_TRUE(file.IsValid()); + ASSERT_FALSE(file.created()); + file.Close(); + + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::DeleteFile(file_name)); + EXPECT_FALSE(base::PathExists(file_name)); + EXPECT_FALSE(NativeFileUtil::PathExists(file_name)); +} + +TEST_F(NativeFileUtilTest, EnsureFileExists) { + base::FilePath file_name = Path("foobar"); + bool created = false; + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::EnsureFileExists(file_name, &created)); + ASSERT_TRUE(created); + + EXPECT_TRUE(FileExists(file_name)); + EXPECT_EQ(0, GetSize(file_name)); + + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::EnsureFileExists(file_name, &created)); + EXPECT_FALSE(created); +} + +TEST_F(NativeFileUtilTest, CreateAndDeleteDirectory) { + base::FilePath dir_name = Path("test_dir"); + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::CreateDirectory(dir_name, + false /* exclusive */, + false /* recursive */)); + + EXPECT_TRUE(NativeFileUtil::DirectoryExists(dir_name)); + EXPECT_TRUE(base::DirectoryExists(dir_name)); + + ASSERT_EQ(base::File::FILE_ERROR_EXISTS, + NativeFileUtil::CreateDirectory(dir_name, + true /* exclusive */, + false /* recursive */)); + + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::DeleteDirectory(dir_name)); + EXPECT_FALSE(base::DirectoryExists(dir_name)); + EXPECT_FALSE(NativeFileUtil::DirectoryExists(dir_name)); +} + +TEST_F(NativeFileUtilTest, TouchFileAndGetFileInfo) { + base::FilePath file_name = Path("test_file"); + base::File::Info native_info; + ASSERT_EQ(base::File::FILE_ERROR_NOT_FOUND, + NativeFileUtil::GetFileInfo(file_name, &native_info)); + + bool created = false; + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::EnsureFileExists(file_name, &created)); + ASSERT_TRUE(created); + + base::File::Info info; + ASSERT_TRUE(base::GetFileInfo(file_name, &info)); + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::GetFileInfo(file_name, &native_info)); + ASSERT_EQ(info.size, native_info.size); + ASSERT_EQ(info.is_directory, native_info.is_directory); + ASSERT_EQ(info.is_symbolic_link, native_info.is_symbolic_link); + ASSERT_EQ(info.last_modified, native_info.last_modified); + ASSERT_EQ(info.last_accessed, native_info.last_accessed); + ASSERT_EQ(info.creation_time, native_info.creation_time); + + const base::Time new_accessed = + info.last_accessed + base::TimeDelta::FromHours(10); + const base::Time new_modified = + info.last_modified + base::TimeDelta::FromHours(5); + + EXPECT_EQ(base::File::FILE_OK, + NativeFileUtil::Touch(file_name, + new_accessed, new_modified)); + + ASSERT_TRUE(base::GetFileInfo(file_name, &info)); + EXPECT_EQ(new_accessed, info.last_accessed); + EXPECT_EQ(new_modified, info.last_modified); +} + +TEST_F(NativeFileUtilTest, CreateFileEnumerator) { + base::FilePath path_1 = Path("dir1"); + base::FilePath path_2 = Path("file1"); + base::FilePath path_11 = Path("dir1").AppendASCII("file11"); + base::FilePath path_12 = Path("dir1").AppendASCII("dir12"); + base::FilePath path_121 = + Path("dir1").AppendASCII("dir12").AppendASCII("file121"); + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::CreateDirectory(path_1, false, false)); + bool created = false; + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::EnsureFileExists(path_2, &created)); + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::EnsureFileExists(path_11, &created)); + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::CreateDirectory(path_12, false, false)); + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::EnsureFileExists(path_121, &created)); + + { + std::unique_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator = + NativeFileUtil::CreateFileEnumerator(Path(), false); + std::set<base::FilePath> set; + set.insert(path_1); + set.insert(path_2); + for (base::FilePath path = enumerator->Next(); !path.empty(); + path = enumerator->Next()) + EXPECT_EQ(1U, set.erase(path)); + EXPECT_TRUE(set.empty()); + } + + { + std::unique_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator = + NativeFileUtil::CreateFileEnumerator(Path(), true); + std::set<base::FilePath> set; + set.insert(path_1); + set.insert(path_2); + set.insert(path_11); + set.insert(path_12); + set.insert(path_121); + for (base::FilePath path = enumerator->Next(); !path.empty(); + path = enumerator->Next()) + EXPECT_EQ(1U, set.erase(path)); + EXPECT_TRUE(set.empty()); + } +} + +TEST_F(NativeFileUtilTest, Truncate) { + base::FilePath file_name = Path("truncated"); + bool created = false; + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::EnsureFileExists(file_name, &created)); + ASSERT_TRUE(created); + + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::Truncate(file_name, 1020)); + + EXPECT_TRUE(FileExists(file_name)); + EXPECT_EQ(1020, GetSize(file_name)); +} + +TEST_F(NativeFileUtilTest, CopyFile) { + base::FilePath from_file = Path("fromfile"); + base::FilePath to_file1 = Path("tofile1"); + base::FilePath to_file2 = Path("tofile2"); + const NativeFileUtil::CopyOrMoveMode nosync = NativeFileUtil::COPY_NOSYNC; + const NativeFileUtil::CopyOrMoveMode sync = NativeFileUtil::COPY_SYNC; + bool created = false; + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::Truncate(from_file, 1020)); + + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::CopyOrMoveFile( + from_file, to_file1, FileSystemOperation::OPTION_NONE, nosync)); + + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::CopyOrMoveFile( + from_file, to_file2, FileSystemOperation::OPTION_NONE, sync)); + + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + EXPECT_TRUE(FileExists(to_file1)); + EXPECT_EQ(1020, GetSize(to_file1)); + EXPECT_TRUE(FileExists(to_file2)); + EXPECT_EQ(1020, GetSize(to_file2)); + + base::FilePath dir = Path("dir"); + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::CreateDirectory(dir, false, false)); + ASSERT_TRUE(base::DirectoryExists(dir)); + base::FilePath to_dir_file = dir.AppendASCII("file"); + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::CopyOrMoveFile( + from_file, to_dir_file, + FileSystemOperation::OPTION_NONE, nosync)); + EXPECT_TRUE(FileExists(to_dir_file)); + EXPECT_EQ(1020, GetSize(to_dir_file)); + + // Following tests are error checking. + // Source doesn't exist. + EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, + NativeFileUtil::CopyOrMoveFile( + Path("nonexists"), Path("file"), + FileSystemOperation::OPTION_NONE, nosync)); + + // Source is not a file. + EXPECT_EQ(base::File::FILE_ERROR_NOT_A_FILE, + NativeFileUtil::CopyOrMoveFile( + dir, Path("file"), FileSystemOperation::OPTION_NONE, nosync)); + // Destination is not a file. + EXPECT_EQ(base::File::FILE_ERROR_INVALID_OPERATION, + NativeFileUtil::CopyOrMoveFile( + from_file, dir, FileSystemOperation::OPTION_NONE, nosync)); + // Destination's parent doesn't exist. + EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, + NativeFileUtil::CopyOrMoveFile( + from_file, Path("nodir").AppendASCII("file"), + FileSystemOperation::OPTION_NONE, nosync)); + // Destination's parent is a file. + EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, + NativeFileUtil::CopyOrMoveFile( + from_file, Path("tofile1").AppendASCII("file"), + FileSystemOperation::OPTION_NONE, nosync)); +} + +TEST_F(NativeFileUtilTest, MoveFile) { + base::FilePath from_file = Path("fromfile"); + base::FilePath to_file = Path("tofile"); + const NativeFileUtil::CopyOrMoveMode move = NativeFileUtil::MOVE; + bool created = false; + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + + ASSERT_EQ(base::File::FILE_OK, NativeFileUtil::Truncate(from_file, 1020)); + + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::CopyOrMoveFile( + from_file, to_file, FileSystemOperation::OPTION_NONE, move)); + + EXPECT_FALSE(FileExists(from_file)); + EXPECT_TRUE(FileExists(to_file)); + EXPECT_EQ(1020, GetSize(to_file)); + + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::EnsureFileExists(from_file, &created)); + ASSERT_TRUE(FileExists(from_file)); + ASSERT_EQ(base::File::FILE_OK, NativeFileUtil::Truncate(from_file, 1020)); + + base::FilePath dir = Path("dir"); + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::CreateDirectory(dir, false, false)); + ASSERT_TRUE(base::DirectoryExists(dir)); + base::FilePath to_dir_file = dir.AppendASCII("file"); + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::CopyOrMoveFile( + from_file, to_dir_file, + FileSystemOperation::OPTION_NONE, move)); + EXPECT_FALSE(FileExists(from_file)); + EXPECT_TRUE(FileExists(to_dir_file)); + EXPECT_EQ(1020, GetSize(to_dir_file)); + + // Following is error checking. + // Source doesn't exist. + EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, + NativeFileUtil::CopyOrMoveFile( + Path("nonexists"), Path("file"), + FileSystemOperation::OPTION_NONE, move)); + + // Source is not a file. + EXPECT_EQ(base::File::FILE_ERROR_NOT_A_FILE, + NativeFileUtil::CopyOrMoveFile( + dir, Path("file"), FileSystemOperation::OPTION_NONE, move)); + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::EnsureFileExists(from_file, &created)); + ASSERT_TRUE(FileExists(from_file)); + // Destination is not a file. + EXPECT_EQ(base::File::FILE_ERROR_INVALID_OPERATION, + NativeFileUtil::CopyOrMoveFile( + from_file, dir, FileSystemOperation::OPTION_NONE, move)); + + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::EnsureFileExists(from_file, &created)); + ASSERT_TRUE(FileExists(from_file)); + // Destination's parent doesn't exist. + EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, + NativeFileUtil::CopyOrMoveFile( + from_file, Path("nodir").AppendASCII("file"), + FileSystemOperation::OPTION_NONE, move)); + // Destination's parent is a file. + EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, + NativeFileUtil::CopyOrMoveFile( + from_file, Path("tofile1").AppendASCII("file"), + FileSystemOperation::OPTION_NONE, move)); +} + +TEST_F(NativeFileUtilTest, PreserveLastModified) { + base::FilePath from_file = Path("fromfile"); + base::FilePath to_file1 = Path("tofile1"); + base::FilePath to_file2 = Path("tofile2"); + base::FilePath to_file3 = Path("tofile3"); + bool created = false; + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + EXPECT_TRUE(FileExists(from_file)); + + base::File::Info file_info1; + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::GetFileInfo(from_file, &file_info1)); + + // Test for copy (nosync). + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::CopyOrMoveFile( + from_file, to_file1, + FileSystemOperation::OPTION_PRESERVE_LAST_MODIFIED, + NativeFileUtil::COPY_NOSYNC)); + + base::File::Info file_info2; + ASSERT_TRUE(FileExists(to_file1)); + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::GetFileInfo(to_file1, &file_info2)); + EXPECT_EQ(file_info1.last_modified, file_info2.last_modified); + + // Test for copy (sync). + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::CopyOrMoveFile( + from_file, to_file2, + FileSystemOperation::OPTION_PRESERVE_LAST_MODIFIED, + NativeFileUtil::COPY_SYNC)); + + ASSERT_TRUE(FileExists(to_file2)); + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::GetFileInfo(to_file1, &file_info2)); + EXPECT_EQ(file_info1.last_modified, file_info2.last_modified); + + // Test for move. + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::CopyOrMoveFile( + from_file, to_file3, + FileSystemOperation::OPTION_PRESERVE_LAST_MODIFIED, + NativeFileUtil::MOVE)); + + ASSERT_TRUE(FileExists(to_file3)); + ASSERT_EQ(base::File::FILE_OK, + NativeFileUtil::GetFileInfo(to_file2, &file_info2)); + EXPECT_EQ(file_info1.last_modified, file_info2.last_modified); +} + +} // namespace content diff --git a/chromium/storage/browser/fileapi/quota/quota_backend_impl_unittest.cc b/chromium/storage/browser/fileapi/quota/quota_backend_impl_unittest.cc new file mode 100644 index 00000000000..78e05df0c30 --- /dev/null +++ b/chromium/storage/browser/fileapi/quota/quota_backend_impl_unittest.cc @@ -0,0 +1,276 @@ +// Copyright 2014 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 "storage/browser/fileapi/quota/quota_backend_impl.h" + +#include <stdint.h> + +#include <memory> +#include <string> + +#include "base/files/scoped_temp_dir.h" +#include "base/macros.h" +#include "base/run_loop.h" +#include "base/test/scoped_task_environment.h" +#include "base/threading/thread_task_runner_handle.h" +#include "storage/browser/fileapi/file_system_usage_cache.h" +#include "storage/browser/fileapi/obfuscated_file_util.h" +#include "storage/browser/quota/quota_manager_proxy.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::FileSystemUsageCache; +using storage::ObfuscatedFileUtil; +using storage::QuotaBackendImpl; +using storage::SandboxFileSystemBackendDelegate; + +namespace content { + +namespace { + +const char kOrigin[] = "http://example.com"; + +bool DidReserveQuota(bool accepted, + base::File::Error* error_out, + int64_t* delta_out, + base::File::Error error, + int64_t delta) { + DCHECK(error_out); + DCHECK(delta_out); + *error_out = error; + *delta_out = delta; + return accepted; +} + +class MockQuotaManagerProxy : public storage::QuotaManagerProxy { + public: + MockQuotaManagerProxy() + : QuotaManagerProxy(NULL, NULL), + storage_modified_count_(0), + usage_(0), + quota_(0) {} + + // We don't mock them. + void NotifyOriginInUse(const GURL& origin) override {} + void NotifyOriginNoLongerInUse(const GURL& origin) override {} + void SetUsageCacheEnabled(storage::QuotaClient::ID client_id, + const GURL& origin, + storage::StorageType type, + bool enabled) override {} + + void NotifyStorageModified(storage::QuotaClient::ID client_id, + const GURL& origin, + storage::StorageType type, + int64_t delta) override { + ++storage_modified_count_; + usage_ += delta; + ASSERT_LE(usage_, quota_); + } + + void GetUsageAndQuota(base::SequencedTaskRunner* original_task_runner, + const GURL& origin, + storage::StorageType type, + const UsageAndQuotaCallback& callback) override { + callback.Run(storage::kQuotaStatusOk, usage_, quota_); + } + + int storage_modified_count() { return storage_modified_count_; } + int64_t usage() { return usage_; } + void set_usage(int64_t usage) { usage_ = usage; } + void set_quota(int64_t quota) { quota_ = quota; } + + protected: + ~MockQuotaManagerProxy() override {} + + private: + int storage_modified_count_; + int64_t usage_; + int64_t quota_; + + DISALLOW_COPY_AND_ASSIGN(MockQuotaManagerProxy); +}; + +} // namespace + +class QuotaBackendImplTest : public testing::Test { + public: + QuotaBackendImplTest() + : file_system_usage_cache_(file_task_runner()), + quota_manager_proxy_(new MockQuotaManagerProxy) {} + + void SetUp() override { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + in_memory_env_.reset(leveldb::NewMemEnv(leveldb::Env::Default())); + file_util_.reset(ObfuscatedFileUtil::CreateForTesting( + NULL, data_dir_.GetPath(), in_memory_env_.get(), file_task_runner())); + backend_.reset(new QuotaBackendImpl(file_task_runner(), file_util_.get(), + &file_system_usage_cache_, + quota_manager_proxy_.get())); + } + + void TearDown() override { + backend_.reset(); + quota_manager_proxy_ = NULL; + file_util_.reset(); + base::RunLoop().RunUntilIdle(); + } + + protected: + void InitializeForOriginAndType(const GURL& origin, + storage::FileSystemType type) { + ASSERT_TRUE(file_util_->InitOriginDatabase(origin, true /* create */)); + ASSERT_TRUE(file_util_->origin_database_ != NULL); + + std::string type_string = + SandboxFileSystemBackendDelegate::GetTypeString(type); + base::File::Error error = base::File::FILE_ERROR_FAILED; + base::FilePath path = file_util_->GetDirectoryForOriginAndType( + origin, type_string, true /* create */, &error); + ASSERT_EQ(base::File::FILE_OK, error); + + ASSERT_TRUE(file_system_usage_cache_.UpdateUsage( + GetUsageCachePath(origin, type), 0)); + } + + base::SequencedTaskRunner* file_task_runner() { + return base::ThreadTaskRunnerHandle::Get().get(); + } + + base::FilePath GetUsageCachePath(const GURL& origin, + storage::FileSystemType type) { + base::FilePath path; + base::File::Error error = backend_->GetUsageCachePath(origin, type, &path); + EXPECT_EQ(base::File::FILE_OK, error); + EXPECT_FALSE(path.empty()); + return path; + } + + base::test::ScopedTaskEnvironment scoped_task_environment_; + base::ScopedTempDir data_dir_; + std::unique_ptr<leveldb::Env> in_memory_env_; + std::unique_ptr<ObfuscatedFileUtil> file_util_; + FileSystemUsageCache file_system_usage_cache_; + scoped_refptr<MockQuotaManagerProxy> quota_manager_proxy_; + std::unique_ptr<QuotaBackendImpl> backend_; + + private: + DISALLOW_COPY_AND_ASSIGN(QuotaBackendImplTest); +}; + +TEST_F(QuotaBackendImplTest, ReserveQuota_Basic) { + storage::FileSystemType type = storage::kFileSystemTypeTemporary; + InitializeForOriginAndType(GURL(kOrigin), type); + quota_manager_proxy_->set_quota(10000); + + int64_t delta = 0; + + const int64_t kDelta1 = 1000; + base::File::Error error = base::File::FILE_ERROR_FAILED; + backend_->ReserveQuota(GURL(kOrigin), type, kDelta1, + base::Bind(&DidReserveQuota, true, &error, &delta)); + EXPECT_EQ(base::File::FILE_OK, error); + EXPECT_EQ(kDelta1, delta); + EXPECT_EQ(kDelta1, quota_manager_proxy_->usage()); + + const int64_t kDelta2 = -300; + error = base::File::FILE_ERROR_FAILED; + backend_->ReserveQuota(GURL(kOrigin), type, kDelta2, + base::Bind(&DidReserveQuota, true, &error, &delta)); + EXPECT_EQ(base::File::FILE_OK, error); + EXPECT_EQ(kDelta2, delta); + EXPECT_EQ(kDelta1 + kDelta2, quota_manager_proxy_->usage()); + + EXPECT_EQ(2, quota_manager_proxy_->storage_modified_count()); +} + +TEST_F(QuotaBackendImplTest, ReserveQuota_NoSpace) { + storage::FileSystemType type = storage::kFileSystemTypeTemporary; + InitializeForOriginAndType(GURL(kOrigin), type); + quota_manager_proxy_->set_quota(100); + + int64_t delta = 0; + + const int64_t kDelta = 1000; + base::File::Error error = base::File::FILE_ERROR_FAILED; + backend_->ReserveQuota(GURL(kOrigin), type, kDelta, + base::Bind(&DidReserveQuota, true, &error, &delta)); + EXPECT_EQ(base::File::FILE_OK, error); + EXPECT_EQ(100, delta); + EXPECT_EQ(100, quota_manager_proxy_->usage()); + + EXPECT_EQ(1, quota_manager_proxy_->storage_modified_count()); +} + +TEST_F(QuotaBackendImplTest, ReserveQuota_Revert) { + storage::FileSystemType type = storage::kFileSystemTypeTemporary; + InitializeForOriginAndType(GURL(kOrigin), type); + quota_manager_proxy_->set_quota(10000); + + int64_t delta = 0; + + const int64_t kDelta = 1000; + base::File::Error error = base::File::FILE_ERROR_FAILED; + backend_->ReserveQuota(GURL(kOrigin), type, kDelta, + base::Bind(&DidReserveQuota, false, &error, &delta)); + EXPECT_EQ(base::File::FILE_OK, error); + EXPECT_EQ(kDelta, delta); + EXPECT_EQ(0, quota_manager_proxy_->usage()); + + EXPECT_EQ(2, quota_manager_proxy_->storage_modified_count()); +} + +TEST_F(QuotaBackendImplTest, ReleaseReservedQuota) { + storage::FileSystemType type = storage::kFileSystemTypeTemporary; + InitializeForOriginAndType(GURL(kOrigin), type); + const int64_t kInitialUsage = 2000; + quota_manager_proxy_->set_usage(kInitialUsage); + quota_manager_proxy_->set_quota(10000); + + const int64_t kSize = 1000; + backend_->ReleaseReservedQuota(GURL(kOrigin), type, kSize); + EXPECT_EQ(kInitialUsage - kSize, quota_manager_proxy_->usage()); + + EXPECT_EQ(1, quota_manager_proxy_->storage_modified_count()); +} + +TEST_F(QuotaBackendImplTest, CommitQuotaUsage) { + storage::FileSystemType type = storage::kFileSystemTypeTemporary; + InitializeForOriginAndType(GURL(kOrigin), type); + quota_manager_proxy_->set_quota(10000); + base::FilePath path = GetUsageCachePath(GURL(kOrigin), type); + + const int64_t kDelta1 = 1000; + backend_->CommitQuotaUsage(GURL(kOrigin), type, kDelta1); + EXPECT_EQ(kDelta1, quota_manager_proxy_->usage()); + int64_t usage = 0; + EXPECT_TRUE(file_system_usage_cache_.GetUsage(path, &usage)); + EXPECT_EQ(kDelta1, usage); + + const int64_t kDelta2 = -300; + backend_->CommitQuotaUsage(GURL(kOrigin), type, kDelta2); + EXPECT_EQ(kDelta1 + kDelta2, quota_manager_proxy_->usage()); + usage = 0; + EXPECT_TRUE(file_system_usage_cache_.GetUsage(path, &usage)); + EXPECT_EQ(kDelta1 + kDelta2, usage); + + EXPECT_EQ(2, quota_manager_proxy_->storage_modified_count()); +} + +TEST_F(QuotaBackendImplTest, DirtyCount) { + storage::FileSystemType type = storage::kFileSystemTypeTemporary; + InitializeForOriginAndType(GURL(kOrigin), type); + base::FilePath path = GetUsageCachePath(GURL(kOrigin), type); + + backend_->IncrementDirtyCount(GURL(kOrigin), type); + uint32_t dirty = 0; + ASSERT_TRUE(file_system_usage_cache_.GetDirty(path, &dirty)); + EXPECT_EQ(1u, dirty); + + backend_->DecrementDirtyCount(GURL(kOrigin), type); + ASSERT_TRUE(file_system_usage_cache_.GetDirty(path, &dirty)); + EXPECT_EQ(0u, dirty); +} + +} // namespace content diff --git a/chromium/storage/browser/fileapi/quota/quota_reservation_manager_unittest.cc b/chromium/storage/browser/fileapi/quota/quota_reservation_manager_unittest.cc new file mode 100644 index 00000000000..22158d0342e --- /dev/null +++ b/chromium/storage/browser/fileapi/quota/quota_reservation_manager_unittest.cc @@ -0,0 +1,368 @@ +// Copyright 2014 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 <stdint.h> + +#include <memory> +#include <utility> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/files/file.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/test/scoped_task_environment.h" +#include "base/threading/thread_task_runner_handle.h" +#include "storage/browser/fileapi/quota/open_file_handle.h" +#include "storage/browser/fileapi/quota/quota_reservation.h" +#include "storage/browser/fileapi/quota/quota_reservation_manager.h" +#include "testing/gtest/include/gtest/gtest.h" + +using storage::kFileSystemTypeTemporary; +using storage::OpenFileHandle; +using storage::QuotaReservation; +using storage::QuotaReservationManager; + +namespace content { + +namespace { + +const char kOrigin[] = "http://example.com"; +const storage::FileSystemType kType = kFileSystemTypeTemporary; +const int64_t kInitialFileSize = 1; + +typedef QuotaReservationManager::ReserveQuotaCallback ReserveQuotaCallback; + +int64_t GetFileSize(const base::FilePath& path) { + int64_t size = 0; + base::GetFileSize(path, &size); + return size; +} + +void SetFileSize(const base::FilePath& path, int64_t size) { + base::File file(path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE); + ASSERT_TRUE(file.IsValid()); + ASSERT_TRUE(file.SetLength(size)); +} + +class FakeBackend : public QuotaReservationManager::QuotaBackend { + public: + FakeBackend() + : on_memory_usage_(kInitialFileSize), on_disk_usage_(kInitialFileSize) {} + ~FakeBackend() override {} + + void ReserveQuota(const GURL& origin, + storage::FileSystemType type, + int64_t delta, + const ReserveQuotaCallback& callback) override { + EXPECT_EQ(GURL(kOrigin), origin); + EXPECT_EQ(kType, type); + on_memory_usage_ += delta; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult(callback), base::File::FILE_OK, delta)); + } + + void ReleaseReservedQuota(const GURL& origin, + storage::FileSystemType type, + int64_t size) override { + EXPECT_LE(0, size); + EXPECT_EQ(GURL(kOrigin), origin); + EXPECT_EQ(kType, type); + on_memory_usage_ -= size; + } + + void CommitQuotaUsage(const GURL& origin, + storage::FileSystemType type, + int64_t delta) override { + EXPECT_EQ(GURL(kOrigin), origin); + EXPECT_EQ(kType, type); + on_disk_usage_ += delta; + on_memory_usage_ += delta; + } + + void IncrementDirtyCount(const GURL& origin, + storage::FileSystemType type) override {} + void DecrementDirtyCount(const GURL& origin, + storage::FileSystemType type) override {} + + int64_t on_memory_usage() { return on_memory_usage_; } + int64_t on_disk_usage() { return on_disk_usage_; } + + private: + int64_t on_memory_usage_; + int64_t on_disk_usage_; + + DISALLOW_COPY_AND_ASSIGN(FakeBackend); +}; + +class FakeWriter { + public: + explicit FakeWriter(std::unique_ptr<OpenFileHandle> handle) + : handle_(std::move(handle)), + path_(handle_->platform_path()), + max_written_offset_(handle_->GetEstimatedFileSize()), + append_mode_write_amount_(0), + dirty_(false) {} + + ~FakeWriter() { + if (handle_) + EXPECT_FALSE(dirty_); + } + + int64_t Truncate(int64_t length) { + int64_t consumed = 0; + + if (max_written_offset_ < length) { + consumed = length - max_written_offset_; + max_written_offset_ = length; + } + SetFileSize(path_, length); + return consumed; + } + + int64_t Write(int64_t max_offset) { + dirty_ = true; + + int64_t consumed = 0; + if (max_written_offset_ < max_offset) { + consumed = max_offset - max_written_offset_; + max_written_offset_ = max_offset; + } + if (GetFileSize(path_) < max_offset) + SetFileSize(path_, max_offset); + return consumed; + } + + int64_t Append(int64_t amount) { + dirty_ = true; + append_mode_write_amount_ += amount; + SetFileSize(path_, GetFileSize(path_) + amount); + return amount; + } + + void ReportUsage() { + handle_->UpdateMaxWrittenOffset(max_written_offset_); + handle_->AddAppendModeWriteAmount(append_mode_write_amount_); + max_written_offset_ = handle_->GetEstimatedFileSize(); + append_mode_write_amount_ = 0; + dirty_ = false; + } + + void ClearWithoutUsageReport() { handle_.reset(); } + + private: + std::unique_ptr<OpenFileHandle> handle_; + base::FilePath path_; + int64_t max_written_offset_; + int64_t append_mode_write_amount_; + bool dirty_; +}; + +void ExpectSuccess(bool* done, base::File::Error error) { + EXPECT_FALSE(*done); + *done = true; + EXPECT_EQ(base::File::FILE_OK, error); +} + +void RefreshReservation(QuotaReservation* reservation, int64_t size) { + DCHECK(reservation); + + bool done = false; + reservation->RefreshReservation(size, base::Bind(&ExpectSuccess, &done)); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(done); +} + +} // namespace + +class QuotaReservationManagerTest : public testing::Test { + public: + QuotaReservationManagerTest() {} + ~QuotaReservationManagerTest() override {} + + void SetUp() override { + ASSERT_TRUE(work_dir_.CreateUniqueTempDir()); + file_path_ = work_dir_.GetPath().Append(FILE_PATH_LITERAL("hoge")); + SetFileSize(file_path_, kInitialFileSize); + + std::unique_ptr<QuotaReservationManager::QuotaBackend> backend( + new FakeBackend); + reservation_manager_.reset(new QuotaReservationManager(std::move(backend))); + } + + void TearDown() override { reservation_manager_.reset(); } + + FakeBackend* fake_backend() { + return static_cast<FakeBackend*>(reservation_manager_->backend_.get()); + } + + QuotaReservationManager* reservation_manager() { + return reservation_manager_.get(); + } + + const base::FilePath& file_path() const { return file_path_; } + + private: + base::test::ScopedTaskEnvironment scoped_task_environment_; + base::ScopedTempDir work_dir_; + base::FilePath file_path_; + std::unique_ptr<QuotaReservationManager> reservation_manager_; + + DISALLOW_COPY_AND_ASSIGN(QuotaReservationManagerTest); +}; + +TEST_F(QuotaReservationManagerTest, BasicTest) { + scoped_refptr<QuotaReservation> reservation = + reservation_manager()->CreateReservation(GURL(kOrigin), kType); + + { + RefreshReservation(reservation.get(), 10 + 20 + 3); + int64_t cached_reserved_quota = reservation->remaining_quota(); + FakeWriter writer(reservation->GetOpenFileHandle(file_path())); + + cached_reserved_quota -= writer.Write(kInitialFileSize + 10); + EXPECT_LE(0, cached_reserved_quota); + cached_reserved_quota -= writer.Append(20); + EXPECT_LE(0, cached_reserved_quota); + + writer.ReportUsage(); + } + + EXPECT_EQ(3, reservation->remaining_quota()); + EXPECT_EQ(kInitialFileSize + 10 + 20, GetFileSize(file_path())); + EXPECT_EQ(kInitialFileSize + 10 + 20, fake_backend()->on_disk_usage()); + EXPECT_EQ(kInitialFileSize + 10 + 20 + 3, fake_backend()->on_memory_usage()); + + { + RefreshReservation(reservation.get(), 5); + FakeWriter writer(reservation->GetOpenFileHandle(file_path())); + + EXPECT_EQ(0, writer.Truncate(3)); + + writer.ReportUsage(); + } + + EXPECT_EQ(5, reservation->remaining_quota()); + EXPECT_EQ(3, GetFileSize(file_path())); + EXPECT_EQ(3, fake_backend()->on_disk_usage()); + EXPECT_EQ(3 + 5, fake_backend()->on_memory_usage()); + + reservation = NULL; + + EXPECT_EQ(3, fake_backend()->on_memory_usage()); +} + +TEST_F(QuotaReservationManagerTest, MultipleWriter) { + scoped_refptr<QuotaReservation> reservation = + reservation_manager()->CreateReservation(GURL(kOrigin), kType); + + { + RefreshReservation(reservation.get(), 10 + 20 + 30 + 40 + 5); + int64_t cached_reserved_quota = reservation->remaining_quota(); + FakeWriter writer1(reservation->GetOpenFileHandle(file_path())); + FakeWriter writer2(reservation->GetOpenFileHandle(file_path())); + FakeWriter writer3(reservation->GetOpenFileHandle(file_path())); + + cached_reserved_quota -= writer1.Write(kInitialFileSize + 10); + EXPECT_LE(0, cached_reserved_quota); + cached_reserved_quota -= writer2.Write(kInitialFileSize + 20); + cached_reserved_quota -= writer3.Append(30); + EXPECT_LE(0, cached_reserved_quota); + cached_reserved_quota -= writer3.Append(40); + EXPECT_LE(0, cached_reserved_quota); + + writer1.ReportUsage(); + writer2.ReportUsage(); + writer3.ReportUsage(); + } + + EXPECT_EQ(kInitialFileSize + 20 + 30 + 40, GetFileSize(file_path())); + EXPECT_EQ(kInitialFileSize + 10 + 20 + 30 + 40 + 5, + fake_backend()->on_memory_usage()); + EXPECT_EQ(kInitialFileSize + 20 + 30 + 40, fake_backend()->on_disk_usage()); + + reservation = NULL; + + EXPECT_EQ(kInitialFileSize + 20 + 30 + 40, fake_backend()->on_disk_usage()); +} + +TEST_F(QuotaReservationManagerTest, MultipleClient) { + scoped_refptr<QuotaReservation> reservation1 = + reservation_manager()->CreateReservation(GURL(kOrigin), kType); + RefreshReservation(reservation1.get(), 10); + int64_t cached_reserved_quota1 = reservation1->remaining_quota(); + + scoped_refptr<QuotaReservation> reservation2 = + reservation_manager()->CreateReservation(GURL(kOrigin), kType); + RefreshReservation(reservation2.get(), 20); + int64_t cached_reserved_quota2 = reservation2->remaining_quota(); + + std::unique_ptr<FakeWriter> writer1( + new FakeWriter(reservation1->GetOpenFileHandle(file_path()))); + + std::unique_ptr<FakeWriter> writer2( + new FakeWriter(reservation2->GetOpenFileHandle(file_path()))); + + cached_reserved_quota1 -= writer1->Write(kInitialFileSize + 10); + EXPECT_LE(0, cached_reserved_quota1); + + cached_reserved_quota2 -= writer2->Append(20); + EXPECT_LE(0, cached_reserved_quota2); + + writer1->ReportUsage(); + RefreshReservation(reservation1.get(), 2); + cached_reserved_quota1 = reservation1->remaining_quota(); + + writer2->ReportUsage(); + RefreshReservation(reservation2.get(), 3); + cached_reserved_quota2 = reservation2->remaining_quota(); + + writer1.reset(); + writer2.reset(); + + EXPECT_EQ(kInitialFileSize + 10 + 20, GetFileSize(file_path())); + EXPECT_EQ(kInitialFileSize + 10 + 20 + 2 + 3, + fake_backend()->on_memory_usage()); + EXPECT_EQ(kInitialFileSize + 10 + 20, fake_backend()->on_disk_usage()); + + reservation1 = NULL; + EXPECT_EQ(kInitialFileSize + 10 + 20 + 3, fake_backend()->on_memory_usage()); + + reservation2 = NULL; + EXPECT_EQ(kInitialFileSize + 10 + 20, fake_backend()->on_memory_usage()); +} + +TEST_F(QuotaReservationManagerTest, ClientCrash) { + scoped_refptr<QuotaReservation> reservation1 = + reservation_manager()->CreateReservation(GURL(kOrigin), kType); + RefreshReservation(reservation1.get(), 15); + + scoped_refptr<QuotaReservation> reservation2 = + reservation_manager()->CreateReservation(GURL(kOrigin), kType); + RefreshReservation(reservation2.get(), 20); + + { + FakeWriter writer(reservation1->GetOpenFileHandle(file_path())); + + writer.Write(kInitialFileSize + 10); + + reservation1->OnClientCrash(); + writer.ClearWithoutUsageReport(); + } + reservation1 = NULL; + + EXPECT_EQ(kInitialFileSize + 10, GetFileSize(file_path())); + EXPECT_EQ(kInitialFileSize + 15 + 20, fake_backend()->on_memory_usage()); + EXPECT_EQ(kInitialFileSize + 10, fake_backend()->on_disk_usage()); + + reservation2 = NULL; + EXPECT_EQ(kInitialFileSize + 10, fake_backend()->on_memory_usage()); +} + +} // namespace content diff --git a/chromium/storage/browser/fileapi/sandbox_isolated_origin_database_unittest.cc b/chromium/storage/browser/fileapi/sandbox_isolated_origin_database_unittest.cc new file mode 100644 index 00000000000..2b9bd748ae8 --- /dev/null +++ b/chromium/storage/browser/fileapi/sandbox_isolated_origin_database_unittest.cc @@ -0,0 +1,42 @@ +// Copyright 2014 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 "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "storage/browser/fileapi/sandbox_isolated_origin_database.h" +#include "storage/browser/fileapi/sandbox_origin_database.h" +#include "testing/gtest/include/gtest/gtest.h" + +using storage::SandboxIsolatedOriginDatabase; + +namespace content { + +namespace { +const base::FilePath::CharType kOriginDirectory[] = FILE_PATH_LITERAL("iso"); +} // namespace + +TEST(SandboxIsolatedOriginDatabaseTest, BasicTest) { + base::ScopedTempDir dir; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + + std::string kOrigin("origin"); + SandboxIsolatedOriginDatabase database(kOrigin, dir.GetPath(), + base::FilePath(kOriginDirectory)); + + EXPECT_TRUE(database.HasOriginPath(kOrigin)); + + base::FilePath path1, path2; + + EXPECT_FALSE(database.GetPathForOrigin(std::string(), &path1)); + EXPECT_FALSE(database.GetPathForOrigin("foo", &path1)); + + EXPECT_TRUE(database.HasOriginPath(kOrigin)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin, &path1)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin, &path2)); + EXPECT_FALSE(path1.empty()); + EXPECT_FALSE(path2.empty()); + EXPECT_EQ(path1, path2); +} + +} // namespace content diff --git a/chromium/storage/browser/fileapi/sandbox_prioritized_origin_database_unittest.cc b/chromium/storage/browser/fileapi/sandbox_prioritized_origin_database_unittest.cc new file mode 100644 index 00000000000..e21f418930e --- /dev/null +++ b/chromium/storage/browser/fileapi/sandbox_prioritized_origin_database_unittest.cc @@ -0,0 +1,216 @@ +// Copyright 2014 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 "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "storage/browser/fileapi/sandbox_origin_database.h" +#include "storage/browser/fileapi/sandbox_prioritized_origin_database.h" +#include "testing/gtest/include/gtest/gtest.h" + +using storage::SandboxOriginDatabase; +using storage::SandboxOriginDatabaseInterface; +using storage::SandboxPrioritizedOriginDatabase; + +namespace content { + +TEST(SandboxPrioritizedOriginDatabaseTest, BasicTest) { + base::ScopedTempDir dir; + base::FilePath path; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + + const std::string kOrigin1("origin1"); + const std::string kOrigin2("origin2"); + + SandboxPrioritizedOriginDatabase database(dir.GetPath(), NULL); + + // Set the kOrigin1 as a parimary origin. + EXPECT_TRUE(database.InitializePrimaryOrigin(kOrigin1)); + + // Add two origins. + EXPECT_TRUE(database.GetPathForOrigin(kOrigin1, &path)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin2, &path)); + + // Verify them. + EXPECT_TRUE(database.HasOriginPath(kOrigin1)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin1, &path)); + EXPECT_FALSE(path.empty()); + EXPECT_TRUE(database.HasOriginPath(kOrigin2)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin2, &path)); + EXPECT_FALSE(path.empty()); + + std::vector<SandboxOriginDatabaseInterface::OriginRecord> origins; + database.ListAllOrigins(&origins); + ASSERT_EQ(2U, origins.size()); + EXPECT_TRUE(origins[0].origin == kOrigin1 || + origins[1].origin == kOrigin1); + EXPECT_TRUE(origins[0].origin == kOrigin2 || + origins[1].origin == kOrigin2); + EXPECT_NE(origins[0].path, origins[1].path); + + // Try remove path for kOrigin1. + database.RemovePathForOrigin(kOrigin1); + + // Verify the removal. + EXPECT_FALSE(database.HasOriginPath(kOrigin1)); + EXPECT_TRUE(database.HasOriginPath(kOrigin2)); + database.ListAllOrigins(&origins); + ASSERT_EQ(1U, origins.size()); + EXPECT_EQ(kOrigin2, origins[0].origin); + + // Try remove path for kOrigin2. + database.RemovePathForOrigin(kOrigin2); + + // Verify the removal. + EXPECT_FALSE(database.HasOriginPath(kOrigin1)); + EXPECT_FALSE(database.HasOriginPath(kOrigin2)); + database.ListAllOrigins(&origins); + EXPECT_TRUE(origins.empty()); +} + +TEST(SandboxPrioritizedOriginDatabaseTest, SetPrimaryLaterTest) { + base::ScopedTempDir dir; + base::FilePath path; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + + const std::string kOrigin1("origin1"); + const std::string kOrigin2("origin2"); + + SandboxPrioritizedOriginDatabase database(dir.GetPath(), NULL); + + EXPECT_TRUE(database.GetPrimaryOrigin().empty()); + + EXPECT_TRUE(database.GetPathForOrigin(kOrigin1, &path)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin2, &path)); + + // Set the kOrigin1 as a parimary origin. + EXPECT_TRUE(database.InitializePrimaryOrigin(kOrigin1)); + EXPECT_EQ(kOrigin1, database.GetPrimaryOrigin()); + + // Regardless of whether it is initialized as primary or not + // they should just work. + EXPECT_TRUE(database.HasOriginPath(kOrigin1)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin1, &path)); + EXPECT_FALSE(path.empty()); + EXPECT_TRUE(database.HasOriginPath(kOrigin2)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin2, &path)); + EXPECT_FALSE(path.empty()); +} + +TEST(SandboxPrioritizedOriginDatabaseTest, LostPrimaryOriginFileTest) { + base::ScopedTempDir dir; + base::FilePath path; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + + const std::string kOrigin1("origin1"); + const std::string kData("foo"); + + SandboxPrioritizedOriginDatabase database(dir.GetPath(), NULL); + + EXPECT_TRUE(database.GetPrimaryOrigin().empty()); + + // Set the kOrigin1 as a parimary origin. + EXPECT_TRUE(database.InitializePrimaryOrigin(kOrigin1)); + EXPECT_EQ(kOrigin1, database.GetPrimaryOrigin()); + + // Make sure it works. + EXPECT_TRUE(database.HasOriginPath(kOrigin1)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin1, &path)); + + // Reset the database. + database.DropDatabase(); + + // kOrigin1 should still be marked as primary. + EXPECT_TRUE(database.HasOriginPath(kOrigin1)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin1, &path)); + + // Corrupt the primary origin file. + base::WriteFile(database.primary_origin_file(), kData.data(), kData.size()); + + // Reset the database. + database.DropDatabase(); + + // kOrigin1 is no longer marked as primary, and unfortunately we fail + // to find the data for the origin. + EXPECT_FALSE(database.HasOriginPath(kOrigin1)); +} + +TEST(SandboxPrioritizedOriginDatabaseTest, MigrationTest) { + base::ScopedTempDir dir; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + + const std::string kOrigin1("origin1"); + const std::string kOrigin2("origin2"); + const std::string kFakeDirectoryData1("0123456789"); + const std::string kFakeDirectoryData2("abcde"); + base::FilePath old_dir_db_path1, old_dir_db_path2; + base::FilePath path1, path2; + + // Initialize the directory with two origins using the regular + // SandboxOriginDatabase. + { + SandboxOriginDatabase database_old(dir.GetPath(), NULL); + base::FilePath old_db_path = database_old.GetDatabasePath(); + EXPECT_FALSE(base::PathExists(old_db_path)); + + // Initialize paths for kOrigin1 and kOrigin2. + EXPECT_TRUE(database_old.GetPathForOrigin(kOrigin1, &path1)); + EXPECT_FALSE(path1.empty()); + EXPECT_TRUE(database_old.GetPathForOrigin(kOrigin2, &path2)); + EXPECT_FALSE(path2.empty()); + + EXPECT_TRUE(base::DirectoryExists(old_db_path)); + + // Populate the origin directory with some fake data. + old_dir_db_path1 = dir.GetPath().Append(path1); + ASSERT_TRUE(base::CreateDirectory(old_dir_db_path1)); + EXPECT_EQ(static_cast<int>(kFakeDirectoryData1.size()), + base::WriteFile(old_dir_db_path1.AppendASCII("dummy"), + kFakeDirectoryData1.data(), + kFakeDirectoryData1.size())); + old_dir_db_path2 = dir.GetPath().Append(path2); + ASSERT_TRUE(base::CreateDirectory(old_dir_db_path2)); + EXPECT_EQ(static_cast<int>(kFakeDirectoryData2.size()), + base::WriteFile(old_dir_db_path2.AppendASCII("dummy"), + kFakeDirectoryData2.data(), + kFakeDirectoryData2.size())); + } + + // Re-open the directory using sandboxPrioritizedOriginDatabase. + SandboxPrioritizedOriginDatabase database(dir.GetPath(), NULL); + + // Set the kOrigin1 as a parimary origin. + // (Trying to initialize another origin should fail). + EXPECT_TRUE(database.InitializePrimaryOrigin(kOrigin1)); + EXPECT_FALSE(database.InitializePrimaryOrigin(kOrigin2)); + + EXPECT_EQ(kOrigin1, database.GetPrimaryOrigin()); + + // Regardless of whether the origin is registered as primary or not + // it should just work. + EXPECT_TRUE(database.HasOriginPath(kOrigin1)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin1, &path1)); + EXPECT_TRUE(database.HasOriginPath(kOrigin2)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin2, &path2)); + + // The directory content must be kept (or migrated if necessary) as well. + std::string origin_db_data; + base::FilePath dir_db_path = dir.GetPath().Append(path1); + EXPECT_TRUE(base::PathExists(dir_db_path.AppendASCII("dummy"))); + EXPECT_TRUE(base::ReadFileToString( + dir_db_path.AppendASCII("dummy"), &origin_db_data)); + EXPECT_EQ(kFakeDirectoryData1, origin_db_data); + + origin_db_data.clear(); + dir_db_path = dir.GetPath().Append(path2); + EXPECT_TRUE(base::PathExists(dir_db_path.AppendASCII("dummy"))); + EXPECT_TRUE(base::ReadFileToString( + dir_db_path.AppendASCII("dummy"), &origin_db_data)); + EXPECT_EQ(kFakeDirectoryData2, origin_db_data); + + // After the migration the kOrigin1 directory database path must be gone. + EXPECT_FALSE(base::PathExists(old_dir_db_path1)); + EXPECT_TRUE(base::PathExists(old_dir_db_path2)); +} + +} // namespace content diff --git a/chromium/storage/browser/fileapi/timed_task_helper_unittest.cc b/chromium/storage/browser/fileapi/timed_task_helper_unittest.cc new file mode 100644 index 00000000000..e3475dfe932 --- /dev/null +++ b/chromium/storage/browser/fileapi/timed_task_helper_unittest.cc @@ -0,0 +1,86 @@ +// Copyright 2014 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 <memory> + +#include "base/bind.h" +#include "base/location.h" +#include "base/run_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "storage/browser/fileapi/timed_task_helper.h" +#include "testing/gtest/include/gtest/gtest.h" + +using storage::TimedTaskHelper; + +namespace content { + +namespace { + +class Embedder { + public: + Embedder() + : timer_(base::ThreadTaskRunnerHandle::Get().get()), + timer_fired_(false) {} + + void OnTimerFired() { + timer_fired_ = true; + } + + TimedTaskHelper* timer() { return &timer_; } + bool timer_fired() const { return timer_fired_; } + + private: + TimedTaskHelper timer_; + bool timer_fired_; +}; + +} // namespace + +TEST(TimedTaskHelper, FireTimerWhenAlive) { + base::MessageLoop message_loop; + Embedder embedder; + + ASSERT_FALSE(embedder.timer_fired()); + ASSERT_FALSE(embedder.timer()->IsRunning()); + + embedder.timer()->Start( + FROM_HERE, + base::TimeDelta::FromSeconds(0), + base::Bind(&Embedder::OnTimerFired, base::Unretained(&embedder))); + + ASSERT_TRUE(embedder.timer()->IsRunning()); + embedder.timer()->Reset(); + ASSERT_TRUE(embedder.timer()->IsRunning()); + + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(embedder.timer_fired()); +} + +TEST(TimedTaskHelper, FireTimerWhenAlreadyDeleted) { + base::MessageLoop message_loop; + + // Run message loop after embedder is already deleted to make sure callback + // doesn't cause a crash for use after free. + { + Embedder embedder; + + ASSERT_FALSE(embedder.timer_fired()); + ASSERT_FALSE(embedder.timer()->IsRunning()); + + embedder.timer()->Start( + FROM_HERE, + base::TimeDelta::FromSeconds(0), + base::Bind(&Embedder::OnTimerFired, base::Unretained(&embedder))); + + ASSERT_TRUE(embedder.timer()->IsRunning()); + } + + // At this point the callback is still in the message queue but + // embedder is gone. + base::RunLoop().RunUntilIdle(); +} + +} // namespace content diff --git a/chromium/storage/browser/quota/quota_manager.cc b/chromium/storage/browser/quota/quota_manager.cc index b9452e44db7..7b69183818c 100644 --- a/chromium/storage/browser/quota/quota_manager.cc +++ b/chromium/storage/browser/quota/quota_manager.cc @@ -215,6 +215,7 @@ class QuotaManager::UsageAndQuotaHelper : public QuotaTask { const GURL& origin, StorageType type, bool is_unlimited, + bool is_session_only, bool is_incognito, const UsageAndQuotaCallback& callback) : QuotaTask(manager), @@ -222,6 +223,7 @@ class QuotaManager::UsageAndQuotaHelper : public QuotaTask { callback_(callback), type_(type), is_unlimited_(is_unlimited), + is_session_only_(is_session_only), is_incognito_(is_incognito), weak_factory_(this) {} @@ -300,8 +302,10 @@ class QuotaManager::UsageAndQuotaHelper : public QuotaTask { settings_ = settings; barrier_closure.Run(); if (type_ == kStorageTypeTemporary && !is_unlimited_) { - SetDesiredHostQuota(barrier_closure, kQuotaStatusOk, - settings.per_host_quota); + int64_t host_quota = is_session_only_ + ? settings.session_only_per_host_quota + : settings.per_host_quota; + SetDesiredHostQuota(barrier_closure, kQuotaStatusOk, host_quota); } } @@ -331,6 +335,7 @@ class QuotaManager::UsageAndQuotaHelper : public QuotaTask { QuotaManager::UsageAndQuotaCallback callback_; StorageType type_; bool is_unlimited_; + bool is_session_only_; bool is_incognito_; int64_t available_space_ = 0; int64_t total_space_ = 0; @@ -841,9 +846,12 @@ void QuotaManager::GetUsageAndQuotaForWebApps( return; } LazyInitialize(); + + bool is_session_only = special_storage_policy_ && + special_storage_policy_->IsStorageSessionOnly(origin); UsageAndQuotaHelper* helper = new UsageAndQuotaHelper( - this, origin, type, IsStorageUnlimited(origin, type), is_incognito_, - callback); + this, origin, type, IsStorageUnlimited(origin, type), is_session_only, + is_incognito_, callback); helper->Start(); } diff --git a/chromium/storage/browser/quota/quota_settings.cc b/chromium/storage/browser/quota/quota_settings.cc index 97daf843933..d428822d61c 100644 --- a/chromium/storage/browser/quota/quota_settings.cc +++ b/chromium/storage/browser/quota/quota_settings.cc @@ -7,6 +7,7 @@ #include <algorithm> #include "base/metrics/histogram_macros.h" +#include "base/rand_util.h" #include "base/sys_info.h" #define UMA_HISTOGRAM_MBYTES(name, sample) \ @@ -15,16 +16,35 @@ namespace storage { +namespace { + +// Skews |value| by +/- |percent|. +int64_t RandomizeByPercent(int64_t value, int percent) { + double random_percent = (base::RandDouble() - 0.5) * percent * 2; + return value + (value * (random_percent / 100.0)); +} + +} // anonymous namespace + base::Optional<storage::QuotaSettings> CalculateNominalDynamicSettings( const base::FilePath& partition_path, bool is_incognito) { const int64_t kMBytes = 1024 * 1024; + const int kRandomizedPercentage = 10; if (is_incognito) { + // The incognito pool size is a fraction of the amount of system memory, + // and the amount is capped to a hard limit. + const double kIncognitoPoolSizeRatio = 0.1; // 10% + const int64_t kMaxIncognitoPoolSize = 300 * kMBytes; + storage::QuotaSettings settings; - settings.pool_size = - std::min(300 * kMBytes, base::SysInfo::AmountOfPhysicalMemory() / 10); + settings.pool_size = std::min( + RandomizeByPercent(kMaxIncognitoPoolSize, kRandomizedPercentage), + static_cast<int64_t>(base::SysInfo::AmountOfPhysicalMemory() * + kIncognitoPoolSizeRatio)); settings.per_host_quota = settings.pool_size / 3; + settings.session_only_per_host_quota = settings.per_host_quota; settings.refresh_interval = base::TimeDelta::Max(); return settings; } @@ -46,6 +66,11 @@ base::Optional<storage::QuotaSettings> CalculateNominalDynamicSettings( // utilized by a single host (ie. 5 for 20%). const int kPerHostTemporaryPortion = 5; + // SessionOnly (or ephemeral) origins are allotted a fraction of what + // normal origins are provided, and the amount is capped to a hard limit. + const double kSessionOnlyHostQuotaRatio = 0.1; // 10% + const int64_t kMaxSessionOnlyHostQuota = 300 * kMBytes; + // os_accomodation is an estimate of how much storage is needed for // the os and essential application code outside of the browser. const int64_t kDefaultOSAccomodation = @@ -81,6 +106,10 @@ base::Optional<storage::QuotaSettings> CalculateNominalDynamicSettings( settings.should_remain_available = total * kShouldRemainAvailableRatio; settings.must_remain_available = total * kMustRemainAvailableRatio; settings.per_host_quota = pool_size / kPerHostTemporaryPortion; + settings.session_only_per_host_quota = std::min( + RandomizeByPercent(kMaxSessionOnlyHostQuota, kRandomizedPercentage), + static_cast<int64_t>(settings.per_host_quota * + kSessionOnlyHostQuotaRatio)); settings.refresh_interval = base::TimeDelta::FromSeconds(60); return settings; } diff --git a/chromium/storage/browser/quota/quota_settings.h b/chromium/storage/browser/quota/quota_settings.h index 5207eb6c9b1..19236cab12c 100644 --- a/chromium/storage/browser/quota/quota_settings.h +++ b/chromium/storage/browser/quota/quota_settings.h @@ -24,6 +24,7 @@ struct QuotaSettings { int64_t must_remain_available) : pool_size(pool_size), per_host_quota(per_host_quota), + session_only_per_host_quota(per_host_quota), should_remain_available(should_remain_available), must_remain_available(must_remain_available) {} @@ -36,6 +37,10 @@ struct QuotaSettings { // value must be less than or equal to the pool_size. int64_t per_host_quota = 0; + // The amount allotted to origins that are considered session only + // according to the SpecialStoragePolicy provided by the embedder. + int64_t session_only_per_host_quota = 0; + // The amount of space that should remain available on the storage // volume. As the volume approaches this limit, the quota system gets // more aggressive about evicting data. diff --git a/chromium/storage/common/fileapi/file_system_types.h b/chromium/storage/common/fileapi/file_system_types.h index cc4e1013b4b..6ea07216f14 100644 --- a/chromium/storage/common/fileapi/file_system_types.h +++ b/chromium/storage/common/fileapi/file_system_types.h @@ -24,11 +24,11 @@ enum FileSystemType { // They are sandboxed filesystems; all the files in the filesystems are // placed under the profile directory with path obfuscation and quota // enforcement. - kFileSystemTypeTemporary = blink::WebFileSystemTypeTemporary, - kFileSystemTypePersistent = blink::WebFileSystemTypePersistent, + kFileSystemTypeTemporary = blink::kWebFileSystemTypeTemporary, + kFileSystemTypePersistent = blink::kWebFileSystemTypePersistent, // Indicates non-sandboxed isolated filesystem. - kFileSystemTypeIsolated = blink::WebFileSystemTypeIsolated, + kFileSystemTypeIsolated = blink::kWebFileSystemTypeIsolated, // Indicates filesystems that are mounted externally via // ExternalMountPoints with a well-known mount name. The mounted @@ -36,7 +36,7 @@ enum FileSystemType { // non-sandboxed removable media folder with a name 'removable', while // chrome.syncFileSystem mounts a sandboxed filesystem with a name // 'syncfs'.) - kFileSystemTypeExternal = blink::WebFileSystemTypeExternal, + kFileSystemTypeExternal = blink::kWebFileSystemTypeExternal, // ------------------------------------------------------------------------ // Marks the beginning of internal type enum. (This is not the actual fs type) diff --git a/chromium/storage/common/fileapi/file_system_util.cc b/chromium/storage/common/fileapi/file_system_util.cc index fa6aaa08ce1..4156e5a337b 100644 --- a/chromium/storage/common/fileapi/file_system_util.cc +++ b/chromium/storage/common/fileapi/file_system_util.cc @@ -356,28 +356,28 @@ blink::WebFileError FileErrorToWebFileError( base::File::Error error_code) { switch (error_code) { case base::File::FILE_ERROR_NOT_FOUND: - return blink::WebFileErrorNotFound; + return blink::kWebFileErrorNotFound; case base::File::FILE_ERROR_INVALID_OPERATION: case base::File::FILE_ERROR_EXISTS: case base::File::FILE_ERROR_NOT_EMPTY: - return blink::WebFileErrorInvalidModification; + return blink::kWebFileErrorInvalidModification; case base::File::FILE_ERROR_NOT_A_DIRECTORY: case base::File::FILE_ERROR_NOT_A_FILE: - return blink::WebFileErrorTypeMismatch; + return blink::kWebFileErrorTypeMismatch; case base::File::FILE_ERROR_ACCESS_DENIED: - return blink::WebFileErrorNoModificationAllowed; + return blink::kWebFileErrorNoModificationAllowed; case base::File::FILE_ERROR_FAILED: - return blink::WebFileErrorInvalidState; + return blink::kWebFileErrorInvalidState; case base::File::FILE_ERROR_ABORT: - return blink::WebFileErrorAbort; + return blink::kWebFileErrorAbort; case base::File::FILE_ERROR_SECURITY: - return blink::WebFileErrorSecurity; + return blink::kWebFileErrorSecurity; case base::File::FILE_ERROR_NO_SPACE: - return blink::WebFileErrorQuotaExceeded; + return blink::kWebFileErrorQuotaExceeded; case base::File::FILE_ERROR_INVALID_URL: - return blink::WebFileErrorEncoding; + return blink::kWebFileErrorEncoding; default: - return blink::WebFileErrorInvalidModification; + return blink::kWebFileErrorInvalidModification; } } @@ -386,19 +386,19 @@ bool GetFileSystemPublicType( blink::WebFileSystemType* type) { DCHECK(type); if (type_string == "Temporary") { - *type = blink::WebFileSystemTypeTemporary; + *type = blink::kWebFileSystemTypeTemporary; return true; } if (type_string == "Persistent") { - *type = blink::WebFileSystemTypePersistent; + *type = blink::kWebFileSystemTypePersistent; return true; } if (type_string == "Isolated") { - *type = blink::WebFileSystemTypeIsolated; + *type = blink::kWebFileSystemTypeIsolated; return true; } if (type_string == "External") { - *type = blink::WebFileSystemTypeExternal; + *type = blink::kWebFileSystemTypeExternal; return true; } NOTREACHED(); diff --git a/chromium/storage/common/quota/quota_status_code.h b/chromium/storage/common/quota/quota_status_code.h index cfdee2999b6..c55f8a5670c 100644 --- a/chromium/storage/common/quota/quota_status_code.h +++ b/chromium/storage/common/quota/quota_status_code.h @@ -12,11 +12,11 @@ namespace storage { enum QuotaStatusCode { kQuotaStatusOk = 0, - kQuotaErrorNotSupported = blink::WebStorageQuotaErrorNotSupported, + kQuotaErrorNotSupported = blink::kWebStorageQuotaErrorNotSupported, kQuotaErrorInvalidModification = - blink::WebStorageQuotaErrorInvalidModification, - kQuotaErrorInvalidAccess = blink::WebStorageQuotaErrorInvalidAccess, - kQuotaErrorAbort = blink::WebStorageQuotaErrorAbort, + blink::kWebStorageQuotaErrorInvalidModification, + kQuotaErrorInvalidAccess = blink::kWebStorageQuotaErrorInvalidAccess, + kQuotaErrorAbort = blink::kWebStorageQuotaErrorAbort, kQuotaStatusUnknown = -1, kQuotaStatusLast = kQuotaErrorAbort, |