[go: nahoru, domu]

blob: 00652e0e6a0cc8aa9a4082aad0210c58f8558c85 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// 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 <string>
#include <tuple>
#include <utility>
#include <vector>
#include "base/feature_list.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_restrictions.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/system/data_pipe_utils.h"
#include "mojo/public/cpp/system/functions.h"
#include "net/base/features.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/blob/blob_url_registry.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/quota/quota_manager_proxy.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/abseil-cpp/absl/types/optional.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,
mojo::PendingReceiver<blink::mojom::BytesProvider> receiver) {
mojo::MakeSelfOwnedReceiver(std::move(impl), std::move(receiver));
}
} // namespace
class BlobRegistryImplTest : public testing::Test {
public:
void SetUp() override {
ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
context_ = std::make_unique<BlobStorageContext>(
data_dir_.GetPath(), data_dir_.GetPath(),
base::ThreadPool::CreateTaskRunner({base::MayBlock()}));
auto storage_policy = base::MakeRefCounted<MockSpecialStoragePolicy>();
if (base::FeatureList::IsEnabled(
net::features::kSupportPartitionedBlobUrl)) {
registry_impl_ =
std::make_unique<BlobRegistryImpl>(context_->AsWeakPtr());
} else {
registry_impl_ = std::make_unique<BlobRegistryImpl>(
context_->AsWeakPtr(), url_registry_.AsWeakPtr(),
base::SequencedTaskRunner::GetCurrentDefault());
}
auto delegate = std::make_unique<MockBlobRegistryDelegate>();
delegate_ptr_ = delegate->AsWeakPtr();
registry_impl_->Bind(registry_.BindNewPipeAndPassReceiver(),
std::move(delegate));
mojo::SetDefaultProcessErrorHandler(base::BindRepeating(
&BlobRegistryImplTest::OnBadMessage, base::Unretained(this)));
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.
disallow_blocking_.emplace();
}
void TearDown() override {
mojo::SetDefaultProcessErrorHandler(base::NullCallback());
// Give pending tasks a chance to run since they may release Mojo bindings
// resources.
base::RunLoop().RunUntilIdle();
}
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::OnceClosure quit_closure, std::string* uuid_out,
const std::string& uuid) {
*uuid_out = uuid;
std::move(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([](base::OnceClosure closure,
BlobStatus status) { std::move(closure).Run(); },
loop.QuitClosure()));
loop.Run();
}
mojo::PendingRemote<blink::mojom::BytesProvider> CreateBytesProvider(
const std::string& bytes) {
if (!bytes_provider_runner_) {
bytes_provider_runner_ =
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()});
}
mojo::PendingRemote<blink::mojom::BytesProvider> 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),
result.InitWithNewPipeAndPassReceiver()));
return result;
}
void CreateBytesProvider(
const std::string& bytes,
mojo::PendingReceiver<blink::mojom::BytesProvider> receiver) {
if (!bytes_provider_runner_) {
bytes_provider_runner_ =
base::ThreadPool::CreateSequencedTaskRunner({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(receiver)));
}
size_t BlobsUnderConstruction() {
return registry_impl_->BlobsUnderConstructionForTesting();
}
size_t BlobsBeingStreamed() {
return registry_impl_->BlobsBeingStreamedForTesting();
}
protected:
base::ScopedTempDir data_dir_;
base::test::TaskEnvironment task_environment_;
absl::optional<base::ScopedDisallowBlocking> disallow_blocking_;
std::unique_ptr<BlobStorageContext> context_;
BlobUrlRegistry url_registry_;
std::unique_ptr<BlobRegistryImpl> registry_impl_;
mojo::Remote<blink::mojom::BlobRegistry> registry_;
base::WeakPtr<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");
{
mojo::Remote<blink::mojom::Blob> blob;
registry_->GetBlobFromUUID(blob.BindNewPipeAndPassReceiver(), kId);
EXPECT_EQ(kId, UUIDFromBlob(blob.get()));
EXPECT_TRUE(blob.is_connected());
}
{
mojo::Remote<blink::mojom::Blob> blob;
registry_->GetBlobFromUUID(blob.BindNewPipeAndPassReceiver(), "invalid id");
blob.FlushForTesting();
EXPECT_FALSE(blob.is_connected());
}
}
TEST_F(BlobRegistryImplTest, GetBlobFromEmptyUUID) {
mojo::Remote<blink::mojom::Blob> blob;
registry_->GetBlobFromUUID(blob.BindNewPipeAndPassReceiver(), "");
blob.FlushForTesting();
EXPECT_EQ(1u, bad_messages_.size());
EXPECT_FALSE(blob.is_connected());
}
TEST_F(BlobRegistryImplTest, Register_EmptyUUID) {
mojo::Remote<blink::mojom::Blob> blob;
EXPECT_FALSE(
registry_->Register(blob.BindNewPipeAndPassReceiver(), "", "", "",
std::vector<blink::mojom::DataElementPtr>()));
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_FALSE(registry_.is_connected());
blob.FlushForTesting();
EXPECT_FALSE(blob.is_connected());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_ExistingUUID) {
const std::string kId = "id";
std::unique_ptr<BlobDataHandle> handle =
CreateBlobFromString(kId, "hello world");
mojo::Remote<blink::mojom::Blob> blob;
EXPECT_FALSE(
registry_->Register(blob.BindNewPipeAndPassReceiver(), kId, "", "",
std::vector<blink::mojom::DataElementPtr>()));
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_FALSE(registry_.is_connected());
blob.FlushForTesting();
EXPECT_FALSE(blob.is_connected());
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";
mojo::Remote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.BindNewPipeAndPassReceiver(), 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_EmptyBytesBlob) {
const std::string kId = "id";
const std::string kContentType = "content/type";
const std::string kContentDisposition = "disposition";
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
0, absl::nullopt, CreateBytesProvider(""))));
mojo::Remote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.BindNewPipeAndPassReceiver(), kId,
kContentType, kContentDisposition,
std::move(elements)));
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;
mojo::PendingRemote<blink::mojom::Blob> referenced_blob_remote;
std::ignore = referenced_blob_remote.InitWithNewPipeAndPassReceiver();
elements.push_back(
blink::mojom::DataElement::NewBlob(blink::mojom::DataElementBlob::New(
std::move(referenced_blob_remote), 0, 16)));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), 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";
mojo::PendingRemote<blink::mojom::Blob> blob_remote;
mojo::PendingReceiver<blink::mojom::Blob> receiver =
blob_remote.InitWithNewPipeAndPassReceiver();
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(blink::mojom::DataElement::NewBlob(
blink::mojom::DataElementBlob::New(std::move(blob_remote), 0, 16)));
EXPECT_TRUE(registry_->Register(std::move(receiver), 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_FALSE(registry_.is_connected());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_CircularReference) {
const std::string kId1 = "id1";
const std::string kId2 = "id2";
const std::string kId3 = "id3";
mojo::PendingRemote<blink::mojom::Blob> blob1_remote, blob2_remote,
blob3_remote;
mojo::PendingReceiver<blink::mojom::Blob> blob_receiver1 =
blob1_remote.InitWithNewPipeAndPassReceiver();
mojo::PendingReceiver<blink::mojom::Blob> blob_receiver2 =
blob2_remote.InitWithNewPipeAndPassReceiver();
mojo::PendingReceiver<blink::mojom::Blob> blob_receiver3 =
blob3_remote.InitWithNewPipeAndPassReceiver();
std::vector<blink::mojom::DataElementPtr> elements1;
elements1.push_back(blink::mojom::DataElement::NewBlob(
blink::mojom::DataElementBlob::New(std::move(blob1_remote), 0, 16)));
std::vector<blink::mojom::DataElementPtr> elements2;
elements2.push_back(blink::mojom::DataElement::NewBlob(
blink::mojom::DataElementBlob::New(std::move(blob2_remote), 0, 16)));
std::vector<blink::mojom::DataElementPtr> elements3;
elements3.push_back(blink::mojom::DataElement::NewBlob(
blink::mojom::DataElementBlob::New(std::move(blob3_remote), 0, 16)));
EXPECT_TRUE(registry_->Register(std::move(blob_receiver1), kId1, "", "",
std::move(elements2)));
EXPECT_TRUE(registry_->Register(std::move(blob_receiver2), kId2, "", "",
std::move(elements3)));
EXPECT_TRUE(registry_->Register(std::move(blob_receiver3), 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_FALSE(registry_.is_connected());
EXPECT_EQ(0u, BlobsUnderConstruction());
#endif
}
TEST_F(BlobRegistryImplTest, Register_NonExistentBlob) {
const std::string kId = "id";
std::vector<blink::mojom::DataElementPtr> elements;
mojo::PendingRemote<blink::mojom::Blob> referenced_blob_remote;
mojo::MakeSelfOwnedReceiver(
std::make_unique<FakeBlob>("mock blob"),
referenced_blob_remote.InitWithNewPipeAndPassReceiver());
elements.push_back(
blink::mojom::DataElement::NewBlob(blink::mojom::DataElementBlob::New(
std::move(referenced_blob_remote), 0, 16)));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), 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_FALSE(registry_.is_connected());
EXPECT_EQ(0u, BlobsUnderConstruction());
}
TEST_F(BlobRegistryImplTest, Register_ValidBlobReferences) {
const std::string kId1 = "id1";
std::unique_ptr<BlobDataHandle> handle =
CreateBlobFromString(kId1, "hello world");
mojo::PendingRemote<blink::mojom::Blob> blob1_remote;
mojo::MakeSelfOwnedReceiver(std::make_unique<FakeBlob>(kId1),
blob1_remote.InitWithNewPipeAndPassReceiver());
const std::string kId2 = "id2";
mojo::PendingRemote<blink::mojom::Blob> blob2_remote;
mojo::PendingReceiver<blink::mojom::Blob> blob_receiver2 =
blob2_remote.InitWithNewPipeAndPassReceiver();
std::vector<blink::mojom::DataElementPtr> elements1;
elements1.push_back(blink::mojom::DataElement::NewBlob(
blink::mojom::DataElementBlob::New(std::move(blob1_remote), 0, 8)));
std::vector<blink::mojom::DataElementPtr> elements2;
elements2.push_back(blink::mojom::DataElement::NewBlob(
blink::mojom::DataElementBlob::New(std::move(blob2_remote), 0, 8)));
mojo::PendingRemote<blink::mojom::Blob> final_blob;
const std::string kId3 = "id3";
EXPECT_TRUE(registry_->Register(final_blob.InitWithNewPipeAndPassReceiver(),
kId3, "", "", std::move(elements2)));
EXPECT_TRUE(registry_->Register(std::move(blob_receiver2), 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_BlobReferencingPendingBlob) {
// Create a blob that is pending population of its data.
const std::string kId1 = "id1";
const std::string kBlob1Data = "foobar";
auto builder = std::make_unique<BlobDataBuilder>(kId1);
builder->set_content_type("text/plain");
BlobDataBuilder::FutureData future_data =
builder->AppendFutureData(kBlob1Data.length());
std::unique_ptr<BlobDataHandle> handle =
context_->BuildBlob(std::move(builder), base::DoNothing());
mojo::PendingRemote<blink::mojom::Blob> blob1_remote;
mojo::MakeSelfOwnedReceiver(std::make_unique<FakeBlob>(kId1),
blob1_remote.InitWithNewPipeAndPassReceiver());
// Now create a blob referencing the pending blob above.
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBlob(blink::mojom::DataElementBlob::New(
std::move(blob1_remote), 0, kBlob1Data.length())));
mojo::PendingRemote<blink::mojom::Blob> final_blob;
const std::string kId2 = "id2";
EXPECT_TRUE(registry_->Register(final_blob.InitWithNewPipeAndPassReceiver(),
kId2, "", "", std::move(elements)));
// Run the runloop to make sure registration of blob kId2 gets far enough
// before blob kId1 is populated.
base::RunLoop().RunUntilIdle();
// Populate the data for the first blob.
future_data.Populate(base::as_bytes(base::make_span(kBlob1Data)));
context_->NotifyTransportComplete(kId1);
// Wait for kId2 to also complete.
std::unique_ptr<BlobDataHandle> handle2 = context_->GetBlobDataFromUUID(kId2);
WaitForBlobCompletion(handle2.get());
// Make sure blob was constructed correctly.
EXPECT_FALSE(handle2->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle2->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId2);
expected_blob_data.AppendData(kBlob1Data);
EXPECT_EQ(expected_blob_data, *handle2->CreateSnapshot());
// And make sure we're not leaking any under construction blobs.
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, absl::nullopt)));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), 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, absl::nullopt)));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), 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_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(""))));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_FALSE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), kId,
"", "", std::move(elements)));
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_FALSE(registry_.is_connected());
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, absl::nullopt, CreateBytesProvider(""))));
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
std::numeric_limits<uint64_t>::max() - 4, absl::nullopt,
CreateBytesProvider(""))));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_FALSE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), kId,
"", "", std::move(elements)));
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_FALSE(registry_.is_connected());
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, absl::nullopt,
CreateBytesProvider(""))));
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
kTestBlobStorageMaxDiskSpace, absl::nullopt,
CreateBytesProvider(""))));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), 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))));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), 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(), absl::nullopt, CreateBytesProvider(kData))));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), 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_InvalidBytesAsReply) {
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(), absl::nullopt, CreateBytesProvider(""))));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), kId,
"", "", std::move(elements)));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
ASSERT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS,
handle->GetBlobStatus());
EXPECT_EQ(1u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(0u, file_request_count_);
EXPECT_EQ(0u, BlobsUnderConstruction());
// Expect 2 bad messages, one for the bad reply by the bytes provider, and one
// for the original register call.
EXPECT_EQ(2u, bad_messages_.size());
}
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(), absl::nullopt, CreateBytesProvider(kData))));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), 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(), absl::nullopt, CreateBytesProvider(kData))));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), 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";
mojo::PendingRemote<blink::mojom::BytesProvider> bytes_provider_remote;
std::ignore = bytes_provider_remote.InitWithNewPipeAndPassReceiver();
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
32, absl::nullopt, std::move(bytes_provider_remote))));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), 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";
mojo::PendingRemote<blink::mojom::BytesProvider> bytes_provider_remote;
auto receiver = bytes_provider_remote.InitWithNewPipeAndPassReceiver();
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
32, absl::nullopt, std::move(bytes_provider_remote))));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), 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.
receiver.reset();
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());
mojo::PendingRemote<blink::mojom::Blob> referenced_blob_remote;
mojo::MakeSelfOwnedReceiver(
std::make_unique<FakeBlob>(kDepId),
referenced_blob_remote.InitWithNewPipeAndPassReceiver());
// 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_remote), 0, kData.size())));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), 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";
mojo::PendingRemote<blink::mojom::BytesProvider> bytes_provider_remote;
auto receiver = bytes_provider_remote.InitWithNewPipeAndPassReceiver();
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
kData.size(), absl::nullopt, std::move(bytes_provider_remote))));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), 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(receiver));
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);
mojo::PendingRemote<blink::mojom::BytesProvider> bytes_provider_remote;
auto receiver = bytes_provider_remote.InitWithNewPipeAndPassReceiver();
std::vector<blink::mojom::DataElementPtr> elements;
elements.push_back(
blink::mojom::DataElement::NewBytes(blink::mojom::DataElementBytes::New(
kData.size(), absl::nullopt, std::move(bytes_provider_remote))));
mojo::PendingRemote<blink::mojom::Blob> blob;
EXPECT_TRUE(registry_->Register(blob.InitWithNewPipeAndPassReceiver(), 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(receiver));
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;
mojo::AssociatedReceiver<blink::mojom::ProgressClient> progress_receiver(
&progress_client);
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),
progress_receiver.BindNewEndpointAndPassRemote(),
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);
mojo::Remote<blink::mojom::Blob> blob_remote(std::move(blob->blob));
EXPECT_EQ(blob->uuid, UUIDFromBlob(blob_remote.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;
mojo::AssociatedReceiver<blink::mojom::ProgressClient> progress_receiver(
&progress_client);
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),
progress_receiver.BindNewEndpointAndPassRemote(),
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::ScopedDataPipeProducerHandle producer_handle1;
mojo::ScopedDataPipeConsumerHandle consumer_handle1;
ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle1, consumer_handle1),
MOJO_RESULT_OK);
mojo::ScopedDataPipeProducerHandle producer_handle2;
mojo::ScopedDataPipeConsumerHandle consumer_handle2;
ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle2, consumer_handle2),
MOJO_RESULT_OK);
registry_->RegisterFromStream("", "", 0, std::move(consumer_handle1),
mojo::NullAssociatedRemote(),
base::DoNothing());
registry_->RegisterFromStream("", "", 0, std::move(consumer_handle2),
mojo::NullAssociatedRemote(),
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