blob: 31be8a2658c7e29f06ec319671d086f407a1192d [file] [log] [blame]
// Copyright 2017 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_registry_impl.h"
#include <limits>
#include <memory>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/scoped_temp_dir.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/sequenced_task_runner.h"
#include "base/task/post_task.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_restrictions.h"
#include "mojo/core/embedder/embedder.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "mojo/public/cpp/system/data_pipe_utils.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "storage/browser/test/fake_blob.h"
#include "storage/browser/test/fake_progress_client.h"
#include "storage/browser/test/mock_blob_registry_delegate.h"
#include "storage/browser/test/mock_bytes_provider.h"
#include "storage/browser/test/mock_special_storage_policy.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/blob/data_element.mojom.h"
#include "third_party/blink/public/mojom/blob/serialized_blob.mojom.h"
namespace storage {
namespace {
const size_t kTestBlobStorageIPCThresholdBytes = 5;
const size_t kTestBlobStorageMaxSharedMemoryBytes = 20;
const size_t kTestBlobStorageMaxBytesDataItemSize = 23;
const size_t kTestBlobStorageMaxBlobMemorySize = 400;
const uint64_t kTestBlobStorageMaxDiskSpace = 4000;
const uint64_t kTestBlobStorageMinFileSizeBytes = 10;
const uint64_t kTestBlobStorageMaxFileSizeBytes = 100;
void BindBytesProvider(std::unique_ptr<MockBytesProvider> impl,
blink::mojom::BytesProviderRequest request) {
mojo::MakeStrongBinding(std::move(impl), std::move(request));
}
} // namespace
class BlobRegistryImplTest : public testing::Test {
public:
void SetUp() override {
ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
context_ = std::make_unique<BlobStorageContext>(
data_dir_.GetPath(),
base::CreateTaskRunnerWithTraits({base::MayBlock()}));
auto storage_policy =
base::MakeRefCounted<content::MockSpecialStoragePolicy>();
file_system_context_ = base::MakeRefCounted<storage::FileSystemContext>(
base::ThreadTaskRunnerHandle::Get().get(),
base::ThreadTaskRunnerHandle::Get().get(),
nullptr /* external_mount_points */, storage_policy.get(),
nullptr /* quota_manager_proxy */,
std::vector<std::unique_ptr<FileSystemBackend>>(),
std::vector<URLRequestAutoMountHandler>(), data_dir_.GetPath(),
FileSystemOptions(FileSystemOptions::PROFILE_MODE_INCOGNITO,
false /* force_in_memory */,
std::vector<std::string>()));
registry_impl_ = std::make_unique<BlobRegistryImpl>(context_->AsWeakPtr(),
file_system_context_);
auto delegate = std::make_unique<MockBlobRegistryDelegate>();
delegate_ptr_ = delegate.get();
registry_impl_->Bind(MakeRequest(&registry_), std::move(delegate));
mojo::core::SetDefaultProcessErrorCallback(base::BindRepeating(
&BlobRegistryImplTest::OnBadMessage, base::Unretained(this)));
storage::BlobStorageLimits limits;
limits.max_ipc_memory_size = kTestBlobStorageIPCThresholdBytes;
limits.max_shared_memory_size = kTestBlobStorageMaxSharedMemoryBytes;
limits.max_bytes_data_item_size = kTestBlobStorageMaxBytesDataItemSize;
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);
// Disallow IO on the main loop.
base::ThreadRestrictions::SetIOAllowed(false);
}
void TearDown() override {
base::ThreadRestrictions::SetIOAllowed(true);
mojo::core::SetDefaultProcessErrorCallback(
mojo::core::ProcessErrorCallback());
}
std::unique_ptr<BlobDataHandle> CreateBlobFromString(
const std::string& uuid,
const std::string& contents) {
auto builder = std::make_unique<BlobDataBuilder>(uuid);
builder->set_content_type("text/plain");
builder->AppendData(contents);
return context_->AddFinishedBlob(std::move(builder));
}
std::string UUIDFromBlob(blink::mojom::Blob* blob) {
base::RunLoop loop;
std::string received_uuid;
blob->GetInternalUUID(base::BindOnce(
[](base::Closure quit_closure, std::string* uuid_out,
const std::string& uuid) {
*uuid_out = uuid;
quit_closure.Run();
},
loop.QuitClosure(), &received_uuid));
loop.Run();
return received_uuid;
}
void OnBadMessage(const std::string& error) {
bad_messages_.push_back(error);
}
void WaitForBlobCompletion(BlobDataHandle* blob_handle) {
base::RunLoop loop;
blob_handle->RunOnConstructionComplete(base::BindOnce(
[](const base::Closure& closure, BlobStatus status) { closure.Run(); },
loop.QuitClosure()));
loop.Run();
}
blink::mojom::BytesProviderPtrInfo CreateBytesProvider(
const std::string& bytes) {
if (!bytes_provider_runner_) {
bytes_provider_runner_ =
base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()});
}
blink::mojom::BytesProviderPtrInfo result;
auto provider = std::make_unique<MockBytesProvider>(
std::vector<uint8_t>(bytes.begin(), bytes.end()), &reply_request_count_,
&stream_request_count_, &file_request_count_);
bytes_provider_runner_->PostTask(
FROM_HERE, base::BindOnce(&BindBytesProvider, std::move(provider),
MakeRequest(&result)));
return result;
}
void CreateBytesProvider(const std::string& bytes,
blink::mojom::BytesProviderRequest request) {
if (!bytes_provider_runner_) {
bytes_provider_runner_ =
base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()});
}
auto provider = std::make_unique<MockBytesProvider>(
std::vector<uint8_t>(bytes.begin(), bytes.end()), &reply_request_count_,
&stream_request_count_, &file_request_count_);
bytes_provider_runner_->PostTask(
FROM_HERE, base::BindOnce(&BindBytesProvider, std::move(provider),
std::move(request)));
}
size_t BlobsUnderConstruction() {
return registry_impl_->BlobsUnderConstructionForTesting();
}
size_t BlobsBeingStreamed() {
return registry_impl_->BlobsBeingStreamedForTesting();
}
protected:
base::ScopedTempDir data_dir_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<BlobStorageContext> context_;
scoped_refptr<storage::FileSystemContext> file_system_context_;
std::unique_ptr<BlobRegistryImpl> registry_impl_;
blink::mojom::BlobRegistryPtr registry_;
MockBlobRegistryDelegate* delegate_ptr_;
scoped_refptr<base::SequencedTaskRunner> bytes_provider_runner_;
size_t reply_request_count_ = 0;
size_t stream_request_count_ = 0;
size_t file_request_count_ = 0;
std::vector<std::string> bad_messages_;
};
TEST_F(BlobRegistryImplTest, GetBlobFromUUID) {
const std::string kId = "id";
std::unique_ptr<BlobDataHandle> handle =
CreateBlobFromString(kId, "hello world");
{
blink::mojom::BlobPtr blob;
registry_->GetBlobFromUUID(MakeRequest(&blob), kId);
EXPECT_EQ(kId, UUIDFromBlob(blob.get()));
EXPECT_FALSE(blob.encountered_error());
}
{
blink::mojom::BlobPtr blob;
registry_->GetBlobFromUUID(MakeRequest(&blob), "invalid id");
blob.FlushForTesting();
EXPECT_TRUE(blob.encountered_error());
}
}
TEST_F(BlobRegistryImplTest, GetBlobFromEmptyUUID) {
blink::mojom::BlobPtr blob;
registry_->GetBlobFromUUID(MakeRequest(&blob), "");
blob.FlushForTesting();
EXPECT_EQ(1u, bad_messages_.size());
EXPECT_TRUE(blob.encountered_error());
}
TEST_F(BlobRegistryImplTest, Register_EmptyUUID) {
blink::mojom::BlobPtr blob;
EXPECT_FALSE(
registry_->Register(MakeRequest(&blob), "", "", "",
std::vector<blink::mojom::DataElementPtr>()));
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_TRUE(registry_.encountered_error());
blob.FlushForTesting();
EXPECT_TRUE(blob.encountered_error());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_ExistingUUID) {
const std::string kId = "id";
std::unique_ptr<BlobDataHandle> handle =
CreateBlobFromString(kId, "hello world");
blink::mojom::BlobPtr blob;
EXPECT_FALSE(
registry_->Register(MakeRequest(&blob), kId, "", "",
std::vector<blink::mojom::DataElementPtr>()));
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_TRUE(registry_.encountered_error());
blob.FlushForTesting();
EXPECT_TRUE(blob.encountered_error());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_EmptyBlob) {
const std::string kId = "id";
const std::string kContentType = "content/type";
const std::string kContentDisposition = "disposition";
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, kContentType,
kContentDisposition,
std::vector<blink::mojom::DataElementPtr>()));
EXPECT_TRUE(bad_messages_.empty());
EXPECT_EQ(kId, UUIDFromBlob(blob.get()));
EXPECT_TRUE(context_->registry().HasEntry(kId));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
EXPECT_EQ(kContentType, handle->content_type());
EXPECT_EQ(kContentDisposition, handle->content_disposition());
EXPECT_EQ(0u, handle->size());
WaitForBlobCompletion(handle.get());
EXPECT_FALSE(handle->IsBroken());
EXPECT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_ReferencedBlobClosedPipe) {
const std::string kId = "id";
std::vector<blink::mojom::DataElementPtr> elements;
blink::mojom::BlobPtrInfo referenced_blob_info;
MakeRequest(&referenced_blob_info);
elements.push_back(
blink::mojom::DataElement::NewBlob(blink::mojom::DataElementBlob::New(
std::move(referenced_blob_info), 0, 16)));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
EXPECT_EQ(BlobStatus::ERR_REFERENCED_BLOB_BROKEN, handle->GetBlobStatus());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_SelfReference) {
const std::string kId = "id";
blink::mojom::BlobPtrInfo blob_info;
blink::mojom::BlobRequest blob_request = MakeRequest(&blob_info);
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(blink::mojom::DataElement::NewBlob(
blink::mojom::DataElementBlob::New(std::move(blob_info), 0, 16)));
EXPECT_TRUE(registry_->Register(std::move(blob_request), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS,
handle->GetBlobStatus());
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_TRUE(registry_.encountered_error());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_CircularReference) {
const std::string kId1 = "id1";
const std::string kId2 = "id2";
const std::string kId3 = "id3";
blink::mojom::BlobPtrInfo blob1_info, blob2_info, blob3_info;
blink::mojom::BlobRequest blob_request1 = MakeRequest(&blob1_info);
blink::mojom::BlobRequest blob_request2 = MakeRequest(&blob2_info);
blink::mojom::BlobRequest blob_request3 = MakeRequest(&blob3_info);
std::vector<blink::mojom::DataElementPtr> elements1;
elements1.push_back(blink::mojom::DataElement::NewBlob(
blink::mojom::DataElementBlob::New(std::move(blob1_info), 0, 16)));
std::vector<blink::mojom::DataElementPtr> elements2;
elements2.push_back(blink::mojom::DataElement::NewBlob(
blink::mojom::DataElementBlob::New(std::move(blob2_info), 0, 16)));
std::vector<blink::mojom::DataElementPtr> elements3;
elements3.push_back(blink::mojom::DataElement::NewBlob(
blink::mojom::DataElementBlob::New(std::move(blob3_info), 0, 16)));
EXPECT_TRUE(registry_->Register(std::move(blob_request1), kId1, "", "",
std::move(elements2)));
EXPECT_TRUE(registry_->Register(std::move(blob_request2), kId2, "", "",
std::move(elements3)));
EXPECT_TRUE(registry_->Register(std::move(blob_request3), kId3, "", "",
std::move(elements1)));
EXPECT_TRUE(bad_messages_.empty());
#if DCHECK_IS_ON()
// Without DCHECKs on this will just hang forever.
std::unique_ptr<BlobDataHandle> handle1 = context_->GetBlobDataFromUUID(kId1);
std::unique_ptr<BlobDataHandle> handle2 = context_->GetBlobDataFromUUID(kId2);
std::unique_ptr<BlobDataHandle> handle3 = context_->GetBlobDataFromUUID(kId3);
WaitForBlobCompletion(handle1.get());
WaitForBlobCompletion(handle2.get());
WaitForBlobCompletion(handle3.get());
EXPECT_TRUE(handle1->IsBroken());
EXPECT_TRUE(handle2->IsBroken());
EXPECT_TRUE(handle3->IsBroken());
BlobStatus status1 = handle1->GetBlobStatus();
BlobStatus status2 = handle2->GetBlobStatus();
BlobStatus status3 = handle3->GetBlobStatus();
EXPECT_TRUE(status1 == BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS ||
status1 == BlobStatus::ERR_REFERENCED_BLOB_BROKEN);
EXPECT_TRUE(status2 == BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS ||
status2 == BlobStatus::ERR_REFERENCED_BLOB_BROKEN);
EXPECT_TRUE(status3 == BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS ||
status3 == BlobStatus::ERR_REFERENCED_BLOB_BROKEN);
EXPECT_EQ((status1 == BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS) +
(status2 == BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS) +
(status3 == BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS),
1);
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_TRUE(registry_.encountered_error());
EXPECT_EQ(0u, BlobsUnderConstruction());
#endif
}
TEST_F(BlobRegistryImplTest, Register_NonExistentBlob) {
const std::string kId = "id";
std::vector<blink::mojom::DataElementPtr> elements;
blink::mojom::BlobPtrInfo referenced_blob_info;
mojo::MakeStrongBinding(std::make_unique<FakeBlob>("mock blob"),
MakeRequest(&referenced_blob_info));
elements.push_back(
blink::mojom::DataElement::NewBlob(blink::mojom::DataElementBlob::New(
std::move(referenced_blob_info), 0, 16)));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS,
handle->GetBlobStatus());
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_TRUE(registry_.encountered_error());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_ValidBlobReferences) {
const std::string kId1 = "id1";
std::unique_ptr<BlobDataHandle> handle =
CreateBlobFromString(kId1, "hello world");
blink::mojom::BlobPtrInfo blob1_info;
mojo::MakeStrongBinding(std::make_unique<FakeBlob>(kId1),
MakeRequest(&blob1_info));
const std::string kId2 = "id2";
blink::mojom::BlobPtrInfo blob2_info;
blink::mojom::BlobRequest blob_request2 = MakeRequest(&blob2_info);
std::vector<blink::mojom::DataElementPtr> elements1;
elements1.push_back(blink::mojom::DataElement::NewBlob(
blink::mojom::DataElementBlob::New(std::move(blob1_info), 0, 8)));
std::vector<blink::mojom::DataElementPtr> elements2;
elements2.push_back(blink::mojom::DataElement::NewBlob(
blink::mojom::DataElementBlob::New(std::move(blob2_info), 0, 8)));
blink::mojom::BlobPtr final_blob;
const std::string kId3 = "id3";
EXPECT_TRUE(registry_->Register(MakeRequest(&final_blob), kId3, "", "",
std::move(elements2)));
EXPECT_TRUE(registry_->Register(std::move(blob_request2), kId2, "", "",
std::move(elements1)));
// kId3 references kId2, kId2 reference kId1, kId1 is a simple string.
std::unique_ptr<BlobDataHandle> handle2 = context_->GetBlobDataFromUUID(kId2);
std::unique_ptr<BlobDataHandle> handle3 = context_->GetBlobDataFromUUID(kId3);
WaitForBlobCompletion(handle2.get());
WaitForBlobCompletion(handle3.get());
EXPECT_FALSE(handle2->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle2->GetBlobStatus());
EXPECT_FALSE(handle3->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle3->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId2);
expected_blob_data.AppendData("hello wo");
EXPECT_EQ(expected_blob_data, *handle2->CreateSnapshot());
EXPECT_EQ(expected_blob_data, *handle3->CreateSnapshot());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_UnreadableFile) {
delegate_ptr_->can_read_file_result = false;
const std::string kId = "id";
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewFile(blink::mojom::DataElementFile::New(
base::FilePath(FILE_PATH_LITERAL("foobar")), 0, 16, base::nullopt)));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
EXPECT_EQ(BlobStatus::ERR_REFERENCED_FILE_UNAVAILABLE,
handle->GetBlobStatus());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_ValidFile) {
delegate_ptr_->can_read_file_result = true;
const std::string kId = "id";
const base::FilePath path(FILE_PATH_LITERAL("foobar"));
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(blink::mojom::DataElement::NewFile(
blink::mojom::DataElementFile::New(path, 0, 16, base::nullopt)));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_FALSE(handle->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId);
expected_blob_data.AppendFile(path, 0, 16, base::Time());
EXPECT_EQ(expected_blob_data, *handle->CreateSnapshot());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_FileSystemFile_InvalidScheme) {
const std::string kId = "id";
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(blink::mojom::DataElement::NewFileFilesystem(
blink::mojom::DataElementFilesystemURL::New(GURL("http://foobar.com/"), 0,
16, base::nullopt)));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
EXPECT_EQ(BlobStatus::ERR_REFERENCED_FILE_UNAVAILABLE,
handle->GetBlobStatus());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_FileSystemFile_UnreadablFile) {
delegate_ptr_->can_read_file_system_file_result = false;
const std::string kId = "id";
const GURL url("filesystem:http://example.com/temporary/myfile.png");
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(blink::mojom::DataElement::NewFileFilesystem(
blink::mojom::DataElementFilesystemURL::New(url, 0, 16, base::nullopt)));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
EXPECT_EQ(BlobStatus::ERR_REFERENCED_FILE_UNAVAILABLE,
handle->GetBlobStatus());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_FileSystemFile_Valid) {
delegate_ptr_->can_read_file_system_file_result = true;
const std::string kId = "id";
const GURL url("filesystem:http://example.com/temporary/myfile.png");
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(blink::mojom::DataElement::NewFileFilesystem(
blink::mojom::DataElementFilesystemURL::New(url, 0, 16, base::nullopt)));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_FALSE(handle->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId);
expected_blob_data.AppendFileSystemFile(url, 0, 16, base::Time(), nullptr);
EXPECT_EQ(expected_blob_data, *handle->CreateSnapshot());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_BytesInvalidEmbeddedData) {
const std::string kId = "id";
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
10, std::vector<uint8_t>(5), CreateBytesProvider(""))));
blink::mojom::BlobPtr blob;
EXPECT_FALSE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_TRUE(registry_.encountered_error());
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS,
handle->GetBlobStatus());
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(0u, file_request_count_);
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_BytesInvalidDataSize) {
const std::string kId = "id";
// Two elements that together are more than uint64_t::max bytes.
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
8, base::nullopt, CreateBytesProvider(""))));
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
std::numeric_limits<uint64_t>::max() - 4, base::nullopt,
CreateBytesProvider(""))));
blink::mojom::BlobPtr blob;
EXPECT_FALSE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_TRUE(registry_.encountered_error());
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS,
handle->GetBlobStatus());
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(0u, file_request_count_);
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_BytesOutOfMemory) {
const std::string kId = "id";
// Two elements that together don't fit in the test quota.
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
kTestBlobStorageMaxDiskSpace, base::nullopt,
CreateBytesProvider(""))));
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
kTestBlobStorageMaxDiskSpace, base::nullopt,
CreateBytesProvider(""))));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
EXPECT_EQ(BlobStatus::ERR_OUT_OF_MEMORY, handle->GetBlobStatus());
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(0u, file_request_count_);
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_ValidEmbeddedBytes) {
const std::string kId = "id";
const std::string kData = "hello world";
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
kData.size(), std::vector<uint8_t>(kData.begin(), kData.end()),
CreateBytesProvider(kData))));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_FALSE(handle->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId);
expected_blob_data.AppendData(kData);
EXPECT_EQ(expected_blob_data, *handle->CreateSnapshot());
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(0u, file_request_count_);
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_ValidBytesAsReply) {
const std::string kId = "id";
const std::string kData = "hello";
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
kData.size(), base::nullopt, CreateBytesProvider(kData))));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_FALSE(handle->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId);
expected_blob_data.AppendData(kData);
EXPECT_EQ(expected_blob_data, *handle->CreateSnapshot());
EXPECT_EQ(1u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(0u, file_request_count_);
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_ValidBytesAsStream) {
const std::string kId = "id";
const std::string kData =
base::RandBytesAsString(kTestBlobStorageMaxSharedMemoryBytes * 3 + 13);
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
kData.size(), base::nullopt, CreateBytesProvider(kData))));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_FALSE(handle->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
size_t offset = 0;
BlobDataBuilder expected_blob_data(kId);
while (offset < kData.size()) {
expected_blob_data.AppendData(
kData.substr(offset, kTestBlobStorageMaxBytesDataItemSize));
offset += kTestBlobStorageMaxBytesDataItemSize;
}
EXPECT_EQ(expected_blob_data, *handle->CreateSnapshot());
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(1u, stream_request_count_);
EXPECT_EQ(0u, file_request_count_);
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_ValidBytesAsFile) {
const std::string kId = "id";
const std::string kData =
base::RandBytesAsString(kTestBlobStorageMaxBlobMemorySize + 42);
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
kData.size(), base::nullopt, CreateBytesProvider(kData))));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_FALSE(handle->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId);
expected_blob_data.AppendData(kData);
size_t expected_file_count =
1 + kData.size() / kTestBlobStorageMaxFileSizeBytes;
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(expected_file_count, file_request_count_);
auto snapshot = handle->CreateSnapshot();
EXPECT_EQ(expected_file_count, snapshot->items().size());
size_t remaining_size = kData.size();
for (const auto& item : snapshot->items()) {
EXPECT_EQ(BlobDataItem::Type::kFile, item->type());
EXPECT_EQ(0u, item->offset());
if (remaining_size > kTestBlobStorageMaxFileSizeBytes)
EXPECT_EQ(kTestBlobStorageMaxFileSizeBytes, item->length());
else
EXPECT_EQ(remaining_size, item->length());
remaining_size -= item->length();
}
EXPECT_EQ(0u, remaining_size);
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_BytesProviderClosedPipe) {
const std::string kId = "id";
blink::mojom::BytesProviderPtrInfo bytes_provider_info;
MakeRequest(&bytes_provider_info);
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
32, base::nullopt, std::move(bytes_provider_info))));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
EXPECT_EQ(BlobStatus::ERR_SOURCE_DIED_IN_TRANSIT, handle->GetBlobStatus());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest,
Register_DefereferencedWhileBuildingBeforeBreaking) {
const std::string kId = "id";
blink::mojom::BytesProviderPtrInfo bytes_provider_info;
auto request = MakeRequest(&bytes_provider_info);
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
32, base::nullopt, std::move(bytes_provider_info))));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
EXPECT_TRUE(context_->registry().HasEntry(kId));
EXPECT_TRUE(context_->GetBlobDataFromUUID(kId)->IsBeingBuilt());
EXPECT_EQ(1u, BlobsUnderConstruction());
// Now drop all references to the blob.
blob.reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(context_->registry().HasEntry(kId));
// Now cause construction to fail, if it would still be going on.
request = nullptr;
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest,
Register_DefereferencedWhileBuildingBeforeResolvingDeps) {
const std::string kId = "id";
const std::string kData = "hello world";
const std::string kDepId = "dep-id";
// Create future blob.
auto blob_handle = context_->AddFutureBlob(
kDepId, "", "", BlobStorageContext::BuildAbortedCallback());
blink::mojom::BlobPtrInfo referenced_blob_info;
mojo::MakeStrongBinding(std::make_unique<FakeBlob>(kDepId),
MakeRequest(&referenced_blob_info));
// Create mojo blob depending on future blob.
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBlob(blink::mojom::DataElementBlob::New(
std::move(referenced_blob_info), 0, kData.size())));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
EXPECT_TRUE(context_->registry().HasEntry(kId));
EXPECT_TRUE(context_->GetBlobDataFromUUID(kId)->IsBeingBuilt());
EXPECT_EQ(1u, BlobsUnderConstruction());
// Now drop all references to the blob.
blob.reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(context_->registry().HasEntry(kId));
// Now cause construction to complete, if it would still be going on.
auto builder = std::make_unique<BlobDataBuilder>(kDepId);
builder->AppendData(kData);
context_->BuildPreregisteredBlob(
std::move(builder), BlobStorageContext::TransportAllowedCallback());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest,
Register_DefereferencedWhileBuildingBeforeTransporting) {
const std::string kId = "id";
const std::string kData = "hello world";
blink::mojom::BytesProviderPtrInfo bytes_provider_info;
auto request = MakeRequest(&bytes_provider_info);
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
kData.size(), base::nullopt, std::move(bytes_provider_info))));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
EXPECT_TRUE(context_->registry().HasEntry(kId));
EXPECT_TRUE(context_->GetBlobDataFromUUID(kId)->IsBeingBuilt());
EXPECT_EQ(1u, BlobsUnderConstruction());
// Now drop all references to the blob.
blob.reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(context_->registry().HasEntry(kId));
// Now cause construction to complete, if it would still be going on.
CreateBytesProvider(kData, std::move(request));
scoped_task_environment_.RunUntilIdle();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest,
Register_DefereferencedWhileBuildingBeforeTransportingByFile) {
const std::string kId = "id";
const std::string kData =
base::RandBytesAsString(kTestBlobStorageMaxBlobMemorySize + 42);
blink::mojom::BytesProviderPtrInfo bytes_provider_info;
auto request = MakeRequest(&bytes_provider_info);
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
kData.size(), base::nullopt, std::move(bytes_provider_info))));
blink::mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
EXPECT_TRUE(context_->registry().HasEntry(kId));
EXPECT_TRUE(context_->GetBlobDataFromUUID(kId)->IsBeingBuilt());
EXPECT_EQ(1u, BlobsUnderConstruction());
// Now drop all references to the blob.
blob.reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(context_->registry().HasEntry(kId));
// Now cause construction to complete, if it would still be going on.
CreateBytesProvider(kData, std::move(request));
scoped_task_environment_.RunUntilIdle();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, RegisterFromStream) {
const std::string kData = "hello world, this is a blob";
const std::string kContentType = "content/type";
const std::string kContentDisposition = "disposition";
FakeProgressClient progress_client;
blink::mojom::ProgressClientAssociatedPtrInfo progress_client_ptr;
mojo::AssociatedBinding<blink::mojom::ProgressClient> progress_binding(
&progress_client, MakeRequest(&progress_client_ptr));
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
mojo::CreateDataPipe(nullptr, &producer, &consumer);
blink::mojom::SerializedBlobPtr blob;
base::RunLoop loop;
registry_->RegisterFromStream(
kContentType, kContentDisposition, kData.length(), std::move(consumer),
std::move(progress_client_ptr),
base::BindLambdaForTesting([&](blink::mojom::SerializedBlobPtr result) {
blob = std::move(result);
loop.Quit();
}));
mojo::BlockingCopyFromString(kData, producer);
producer.reset();
loop.Run();
ASSERT_TRUE(blob);
EXPECT_FALSE(blob->uuid.empty());
EXPECT_EQ(kContentType, blob->content_type);
EXPECT_EQ(kData.length(), blob->size);
ASSERT_TRUE(blob->blob);
blink::mojom::BlobPtr blob_ptr(std::move(blob->blob));
EXPECT_EQ(blob->uuid, UUIDFromBlob(blob_ptr.get()));
EXPECT_EQ(kData.length(), progress_client.total_size);
EXPECT_GE(progress_client.call_count, 1);
EXPECT_EQ(0u, BlobsBeingStreamed());
}
TEST_F(BlobRegistryImplTest, RegisterFromStream_NoDiskSpace) {
const std::string kData =
base::RandBytesAsString(kTestBlobStorageMaxDiskSpace + 1);
const std::string kContentType = "content/type";
const std::string kContentDisposition = "disposition";
FakeProgressClient progress_client;
blink::mojom::ProgressClientAssociatedPtrInfo progress_client_ptr;
mojo::AssociatedBinding<blink::mojom::ProgressClient> progress_binding(
&progress_client, MakeRequest(&progress_client_ptr));
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
mojo::CreateDataPipe(nullptr, &producer, &consumer);
blink::mojom::SerializedBlobPtr blob;
base::RunLoop loop;
registry_->RegisterFromStream(
kContentType, kContentDisposition, kData.length(), std::move(consumer),
std::move(progress_client_ptr),
base::BindLambdaForTesting([&](blink::mojom::SerializedBlobPtr result) {
blob = std::move(result);
loop.Quit();
}));
mojo::BlockingCopyFromString(kData, producer);
producer.reset();
loop.Run();
EXPECT_FALSE(blob);
EXPECT_EQ(0u, BlobsBeingStreamed());
}
TEST_F(BlobRegistryImplTest, DestroyWithUnfinishedStream) {
mojo::DataPipe pipe1, pipe2;
registry_->RegisterFromStream("", "", 0, std::move(pipe1.consumer_handle),
nullptr, base::DoNothing());
registry_->RegisterFromStream("", "", 0, std::move(pipe2.consumer_handle),
nullptr, base::DoNothing());
registry_.FlushForTesting();
// This test just makes sure no crash happens if we're shut down while still
// creating blobs from streams.
}
} // namespace storage