summaryrefslogtreecommitdiff
path: root/chromium/storage
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2017-07-12 14:07:37 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2017-07-17 10:29:26 +0000
commitec02ee4181c49b61fce1c8fb99292dbb8139cc90 (patch)
tree25cde714b2b71eb639d1cd53f5a22e9ba76e14ef /chromium/storage
parentbb09965444b5bb20b096a291445170876225268d (diff)
downloadqtwebengine-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')
-rw-r--r--chromium/storage/browser/BUILD.gn63
-rw-r--r--chromium/storage/browser/blob/blob_data_builder_unittest.cc31
-rw-r--r--chromium/storage/browser/blob/blob_flattener_unittest.cc283
-rw-r--r--chromium/storage/browser/blob/blob_memory_controller_unittest.cc1123
-rw-r--r--chromium/storage/browser/blob/blob_reader_unittest.cc1249
-rw-r--r--chromium/storage/browser/blob/blob_slice_unittest.cc221
-rw-r--r--chromium/storage/browser/blob/blob_storage_context_unittest.cc896
-rw-r--r--chromium/storage/browser/blob/blob_storage_registry_unittest.cc81
-rw-r--r--chromium/storage/browser/blob/blob_transport_request_builder_unittest.cc350
-rw-r--r--chromium/storage/browser/blob/blob_url_request_job_unittest.cc608
-rw-r--r--chromium/storage/browser/blob/shareable_file_reference.cc3
-rw-r--r--chromium/storage/browser/crbug653751_unittest.cc13
-rw-r--r--chromium/storage/browser/database/OWNERS4
-rw-r--r--chromium/storage/browser/database/database_quota_client_unittest.cc277
-rw-r--r--chromium/storage/browser/database/database_util_unittest.cc49
-rw-r--r--chromium/storage/browser/database/databases_table_unittest.cc148
-rw-r--r--chromium/storage/browser/fileapi/external_mount_points_unittest.cc505
-rw-r--r--chromium/storage/browser/fileapi/file_system_url_unittest.cc224
-rw-r--r--chromium/storage/browser/fileapi/file_system_usage_cache_unittest.cc163
-rw-r--r--chromium/storage/browser/fileapi/isolated_context.h2
-rw-r--r--chromium/storage/browser/fileapi/isolated_context_unittest.cc363
-rw-r--r--chromium/storage/browser/fileapi/local_file_stream_reader_unittest.cc283
-rw-r--r--chromium/storage/browser/fileapi/local_file_stream_writer_unittest.cc182
-rw-r--r--chromium/storage/browser/fileapi/native_file_util_unittest.cc409
-rw-r--r--chromium/storage/browser/fileapi/quota/quota_backend_impl_unittest.cc276
-rw-r--r--chromium/storage/browser/fileapi/quota/quota_reservation_manager_unittest.cc368
-rw-r--r--chromium/storage/browser/fileapi/sandbox_isolated_origin_database_unittest.cc42
-rw-r--r--chromium/storage/browser/fileapi/sandbox_prioritized_origin_database_unittest.cc216
-rw-r--r--chromium/storage/browser/fileapi/timed_task_helper_unittest.cc86
-rw-r--r--chromium/storage/browser/quota/quota_manager.cc16
-rw-r--r--chromium/storage/browser/quota/quota_settings.cc33
-rw-r--r--chromium/storage/browser/quota/quota_settings.h5
-rw-r--r--chromium/storage/common/fileapi/file_system_types.h8
-rw-r--r--chromium/storage/common/fileapi/file_system_util.cc28
-rw-r--r--chromium/storage/common/quota/quota_status_code.h8
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,