| Index: content/browser/blob_storage/blob_dispatcher_host_unittest.cc
|
| diff --git a/content/browser/blob_storage/blob_dispatcher_host_unittest.cc b/content/browser/blob_storage/blob_dispatcher_host_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..f55332b949165f41a20c390da22cfaa41493e045
|
| --- /dev/null
|
| +++ b/content/browser/blob_storage/blob_dispatcher_host_unittest.cc
|
| @@ -0,0 +1,930 @@
|
| +// 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 "content/browser/blob_storage/blob_dispatcher_host.h"
|
| +
|
| +#include <vector>
|
| +
|
| +#include "base/command_line.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "base/memory/shared_memory.h"
|
| +#include "base/run_loop.h"
|
| +#include "base/tuple.h"
|
| +#include "content/browser/fileapi/chrome_blob_storage_context.h"
|
| +#include "content/common/fileapi/webblob_messages.h"
|
| +#include "content/public/common/content_switches.h"
|
| +#include "content/public/test/test_browser_context.h"
|
| +#include "content/public/test/test_browser_thread_bundle.h"
|
| +#include "ipc/ipc_sender.h"
|
| +#include "ipc/ipc_test_sink.h"
|
| +#include "ipc/message_filter.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/common/blob_storage/blob_item_bytes_request.h"
|
| +#include "storage/common/blob_storage/blob_item_bytes_response.h"
|
| +#include "storage/common/data_element.h"
|
| +#include "testing/gmock/include/gmock/gmock.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +using storage::BlobDataBuilder;
|
| +using storage::BlobDataHandle;
|
| +using storage::BlobItemBytesRequest;
|
| +using storage::BlobItemBytesResponse;
|
| +using storage::BlobStorageContext;
|
| +using storage::BlobTransportResult;
|
| +using storage::DataElement;
|
| +using storage::IPCBlobCreationCancelCode;
|
| +using RequestMemoryCallback =
|
| + storage::BlobAsyncBuilderHost::RequestMemoryCallback;
|
| +
|
| +namespace content {
|
| +namespace {
|
| +
|
| +const char kContentType[] = "text/plain";
|
| +const char kContentDisposition[] = "content_disposition";
|
| +const char kData[] = "data!!";
|
| +const size_t kDataSize = 6;
|
| +
|
| +const size_t kTestBlobStorageIPCThresholdBytes = 20;
|
| +const size_t kTestBlobStorageMaxSharedMemoryBytes = 50;
|
| +const uint64_t kTestBlobStorageMaxFileSizeBytes = 100;
|
| +
|
| +template <typename T>
|
| +void SetPointerValue(T* pointer, T value) {
|
| + *pointer = value;
|
| +}
|
| +
|
| +class TestableBlobDispatcherHost : public BlobDispatcherHost {
|
| + public:
|
| + TestableBlobDispatcherHost(ChromeBlobStorageContext* blob_storage_context,
|
| + IPC::TestSink* sink)
|
| + : BlobDispatcherHost(blob_storage_context), sink_(sink) {
|
| + this->SetMemoryConstantsForTesting(kTestBlobStorageIPCThresholdBytes,
|
| + kTestBlobStorageMaxSharedMemoryBytes,
|
| + kTestBlobStorageMaxFileSizeBytes);
|
| + }
|
| +
|
| + bool Send(IPC::Message* message) override { return sink_->Send(message); }
|
| +
|
| + void ShutdownForBadMessage() override { shutdown_for_bad_message_ = true; }
|
| +
|
| + bool shutdown_for_bad_message_ = false;
|
| + IPC::TestSink* sink_;
|
| +
|
| + protected:
|
| + ~TestableBlobDispatcherHost() override {}
|
| +
|
| + private:
|
| + friend class base::RefCountedThreadSafe<TestableBlobDispatcherHost>;
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +class BlobDispatcherHostTest : public testing::Test {
|
| + protected:
|
| + BlobDispatcherHostTest()
|
| + : browser_thread_bundle_(),
|
| + browser_context_(),
|
| + chrome_blob_storage_context_(
|
| + ChromeBlobStorageContext::GetFor(&browser_context_)) {
|
| + host_ =
|
| + new TestableBlobDispatcherHost(chrome_blob_storage_context_, &sink_);
|
| + }
|
| + ~BlobDispatcherHostTest() override {}
|
| +
|
| + void SetUp() override {
|
| + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
| + if (!command_line->HasSwitch(switches::kDisableKillAfterBadIPC)) {
|
| + command_line->AppendSwitch(switches::kDisableKillAfterBadIPC);
|
| + }
|
| + // We run the run loop to initialize the chrome blob storage context.
|
| + base::RunLoop().RunUntilIdle();
|
| + context_ = chrome_blob_storage_context_->context();
|
| + DCHECK(context_);
|
| + }
|
| +
|
| + void ExpectBlobNotExist(const std::string& id) {
|
| + EXPECT_FALSE(context_->registry().HasEntry(id));
|
| + EXPECT_FALSE(host_->IsInUseInHost(id));
|
| + EXPECT_FALSE(IsBeingBuiltInHost(id));
|
| + }
|
| +
|
| + void AsyncShortcutBlobTransfer(const std::string& id) {
|
| + sink_.ClearMessages();
|
| + ExpectBlobNotExist(id);
|
| + host_->OnRegisterBlobUUID(id, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| + DataElement element;
|
| + element.SetToBytes(kData, kDataSize);
|
| + std::vector<DataElement> elements = {element};
|
| + host_->OnStartBuildingBlob(id, elements);
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| + ExpectDone(id);
|
| + sink_.ClearMessages();
|
| + }
|
| +
|
| + void AsyncBlobTransfer(const std::string& id) {
|
| + sink_.ClearMessages();
|
| + ExpectBlobNotExist(id);
|
| + host_->OnRegisterBlobUUID(id, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| + DataElement element;
|
| + element.SetToBytesDescription(kDataSize);
|
| + std::vector<DataElement> elements = {element};
|
| + host_->OnStartBuildingBlob(id, elements);
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| +
|
| + // Expect our request.
|
| + std::vector<BlobItemBytesRequest> expected_requests = {
|
| + BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize)};
|
| + ExpectRequest(id, expected_requests);
|
| + sink_.ClearMessages();
|
| +
|
| + // Send results;
|
| + BlobItemBytesResponse response(0);
|
| + std::memcpy(response.allocate_mutable_data(kDataSize), kData, kDataSize);
|
| + std::vector<BlobItemBytesResponse> responses = {response};
|
| + host_->OnMemoryItemResponse(id, responses);
|
| + ExpectDone(id);
|
| + sink_.ClearMessages();
|
| + }
|
| +
|
| + void ExpectAndResetBadMessage() {
|
| + EXPECT_TRUE(host_->shutdown_for_bad_message_);
|
| + host_->shutdown_for_bad_message_ = false;
|
| + }
|
| +
|
| + void ExpectHandleEqualsData(BlobDataHandle* handle,
|
| + const std::vector<DataElement>& data) {
|
| + scoped_ptr<storage::BlobDataSnapshot> snapshot = handle->CreateSnapshot();
|
| + EXPECT_FALSE(handle->IsBeingBuilt());
|
| + for (size_t i = 0; i < data.size(); i++) {
|
| + const DataElement& expected = data[i];
|
| + const DataElement& actual = snapshot->items()[i]->data_element();
|
| + EXPECT_EQ(expected, actual);
|
| + }
|
| + EXPECT_EQ(data.size(), snapshot->items().size());
|
| + }
|
| +
|
| + void ExpectRequest(
|
| + const std::string& expected_uuid,
|
| + const std::vector<BlobItemBytesRequest>& expected_requests) {
|
| + EXPECT_FALSE(
|
| + sink_.GetFirstMessageMatching(BlobStorageMsg_DoneBuildingBlob::ID));
|
| + EXPECT_FALSE(
|
| + sink_.GetFirstMessageMatching(BlobStorageMsg_CancelBuildingBlob::ID));
|
| + const IPC::Message* message =
|
| + sink_.GetUniqueMessageMatching(BlobStorageMsg_RequestMemoryItem::ID);
|
| + ASSERT_TRUE(message);
|
| + base::Tuple<std::string, std::vector<storage::BlobItemBytesRequest>,
|
| + std::vector<base::SharedMemoryHandle>,
|
| + std::vector<IPC::PlatformFileForTransit>>
|
| + args;
|
| + BlobStorageMsg_RequestMemoryItem::Read(message, &args);
|
| + EXPECT_EQ(expected_uuid, base::get<0>(args));
|
| + std::vector<BlobItemBytesRequest> requests = base::get<1>(args);
|
| + ASSERT_EQ(requests.size(), expected_requests.size());
|
| + for (size_t i = 0; i < expected_requests.size(); ++i) {
|
| + EXPECT_EQ(expected_requests[i], requests[i]);
|
| + }
|
| + }
|
| +
|
| + void ExpectRequestWithSharedMemoryHandles(
|
| + const std::string& expected_uuid,
|
| + const std::vector<BlobItemBytesRequest>& expected_requests,
|
| + std::vector<base::SharedMemoryHandle>* shared_memory_handles) {
|
| + EXPECT_FALSE(
|
| + sink_.GetFirstMessageMatching(BlobStorageMsg_DoneBuildingBlob::ID));
|
| + EXPECT_FALSE(
|
| + sink_.GetFirstMessageMatching(BlobStorageMsg_CancelBuildingBlob::ID));
|
| + const IPC::Message* message =
|
| + sink_.GetUniqueMessageMatching(BlobStorageMsg_RequestMemoryItem::ID);
|
| + ASSERT_TRUE(message);
|
| + base::Tuple<std::string, std::vector<storage::BlobItemBytesRequest>,
|
| + std::vector<base::SharedMemoryHandle>,
|
| + std::vector<IPC::PlatformFileForTransit>>
|
| + args;
|
| + BlobStorageMsg_RequestMemoryItem::Read(message, &args);
|
| + EXPECT_EQ(expected_uuid, base::get<0>(args));
|
| + std::vector<BlobItemBytesRequest> requests = base::get<1>(args);
|
| + ASSERT_EQ(requests.size(), expected_requests.size());
|
| + for (size_t i = 0; i < expected_requests.size(); ++i) {
|
| + EXPECT_EQ(expected_requests[i], requests[i]);
|
| + }
|
| + *shared_memory_handles = std::move(base::get<2>(args));
|
| + }
|
| +
|
| + void ExpectCancel(const std::string& expected_uuid,
|
| + IPCBlobCreationCancelCode code) {
|
| + EXPECT_FALSE(
|
| + sink_.GetFirstMessageMatching(BlobStorageMsg_RequestMemoryItem::ID));
|
| + EXPECT_FALSE(
|
| + sink_.GetFirstMessageMatching(BlobStorageMsg_DoneBuildingBlob::ID));
|
| + const IPC::Message* message =
|
| + sink_.GetUniqueMessageMatching(BlobStorageMsg_CancelBuildingBlob::ID);
|
| + ASSERT_TRUE(message);
|
| + base::Tuple<std::string, IPCBlobCreationCancelCode> args;
|
| + BlobStorageMsg_CancelBuildingBlob::Read(message, &args);
|
| + EXPECT_EQ(expected_uuid, base::get<0>(args));
|
| + EXPECT_EQ(code, base::get<1>(args));
|
| + }
|
| +
|
| + void ExpectDone(const std::string& expected_uuid) {
|
| + EXPECT_FALSE(
|
| + sink_.GetFirstMessageMatching(BlobStorageMsg_RequestMemoryItem::ID));
|
| + EXPECT_FALSE(
|
| + sink_.GetFirstMessageMatching(BlobStorageMsg_CancelBuildingBlob::ID));
|
| + const IPC::Message* message =
|
| + sink_.GetUniqueMessageMatching(BlobStorageMsg_DoneBuildingBlob::ID);
|
| + base::Tuple<std::string> args;
|
| + BlobStorageMsg_DoneBuildingBlob::Read(message, &args);
|
| + EXPECT_EQ(expected_uuid, base::get<0>(args));
|
| + }
|
| +
|
| + bool IsBeingBuiltInHost(const std::string& uuid) {
|
| + return host_->async_builder_.IsBeingBuilt(uuid);
|
| + }
|
| +
|
| + IPC::TestSink sink_;
|
| + TestBrowserThreadBundle browser_thread_bundle_;
|
| + TestBrowserContext browser_context_;
|
| + ChromeBlobStorageContext* chrome_blob_storage_context_;
|
| + BlobStorageContext* context_ = nullptr;
|
| + scoped_refptr<TestableBlobDispatcherHost> host_;
|
| +};
|
| +
|
| +TEST_F(BlobDispatcherHostTest, EmptyUUIDs) {
|
| + host_->OnRegisterBlobUUID("", "", "", std::set<std::string>());
|
| + ExpectAndResetBadMessage();
|
| + host_->OnStartBuildingBlob("", std::vector<DataElement>());
|
| + ExpectAndResetBadMessage();
|
| + host_->OnMemoryItemResponse("", std::vector<BlobItemBytesResponse>());
|
| + ExpectAndResetBadMessage();
|
| + host_->OnCancelBuildingBlob("", IPCBlobCreationCancelCode::UNKNOWN);
|
| + ExpectAndResetBadMessage();
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, Shortcut) {
|
| + const std::string kId = "uuid1";
|
| + AsyncShortcutBlobTransfer(kId);
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + scoped_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
|
| + EXPECT_TRUE(handle);
|
| +
|
| + DataElement expected;
|
| + expected.SetToBytes(kData, kDataSize);
|
| + std::vector<DataElement> elements = {expected};
|
| + ExpectHandleEqualsData(handle.get(), elements);
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, RegularTransfer) {
|
| + const std::string kId = "uuid1";
|
| + AsyncBlobTransfer(kId);
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + scoped_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
|
| + EXPECT_TRUE(handle);
|
| +
|
| + DataElement expected;
|
| + expected.SetToBytes(kData, kDataSize);
|
| + std::vector<DataElement> elements = {expected};
|
| + ExpectHandleEqualsData(handle.get(), elements);
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, SharedMemoryTransfer) {
|
| + const std::string kId = "uuid1";
|
| + const size_t kLargeSize = kTestBlobStorageMaxSharedMemoryBytes * 2;
|
| + std::vector<base::SharedMemoryHandle> shared_memory_handles;
|
| +
|
| + ExpectBlobNotExist(kId);
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| + // Grab the handle.
|
| + scoped_ptr<BlobDataHandle> blob_data_handle =
|
| + context_->GetBlobDataFromUUID(kId);
|
| + bool built = false;
|
| + blob_data_handle->RunOnConstructionComplete(
|
| + base::Bind(&SetPointerValue<bool>, &built));
|
| + EXPECT_FALSE(built);
|
| +
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| + DataElement element;
|
| + element.SetToBytesDescription(kLargeSize);
|
| + std::vector<DataElement> elements = {element};
|
| + host_->OnStartBuildingBlob(kId, elements);
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| +
|
| + // Expect our first request.
|
| + std::vector<BlobItemBytesRequest> expected_requests = {
|
| + BlobItemBytesRequest::CreateSharedMemoryRequest(
|
| + 0 /* request_number */, 0 /* renderer_item_index */,
|
| + 0 /* renderer_item_offset */,
|
| + static_cast<uint64_t>(
|
| + kTestBlobStorageMaxSharedMemoryBytes) /* size */,
|
| + 0 /* handle_index */, 0 /* handle_offset */)};
|
| + ExpectRequestWithSharedMemoryHandles(kId, expected_requests,
|
| + &shared_memory_handles);
|
| + sink_.ClearMessages();
|
| +
|
| + // Populate the shared memory.
|
| + EXPECT_EQ(1u, shared_memory_handles.size());
|
| + EXPECT_TRUE(base::SharedMemory::IsHandleValid(shared_memory_handles[0]));
|
| + {
|
| + base::SharedMemory memory(
|
| + base::SharedMemory::DuplicateHandle(shared_memory_handles[0]), false);
|
| + memory.Map(kTestBlobStorageMaxSharedMemoryBytes);
|
| + std::memset(memory.memory(), 'X', kTestBlobStorageMaxSharedMemoryBytes);
|
| + memory.Close();
|
| + }
|
| +
|
| + // Send the confirmation.
|
| + std::vector<BlobItemBytesResponse> responses = {BlobItemBytesResponse(0)};
|
| + host_->OnMemoryItemResponse(kId, responses);
|
| +
|
| + // Expect our second request.
|
| + expected_requests = {BlobItemBytesRequest::CreateSharedMemoryRequest(
|
| + 1 /* request_number */, 0 /* renderer_item_index */,
|
| + static_cast<uint64_t>(
|
| + kTestBlobStorageMaxSharedMemoryBytes) /* renderer_item_offset */,
|
| + static_cast<uint64_t>(kTestBlobStorageMaxSharedMemoryBytes) /* size */,
|
| + 0 /* handle_index */, 0 /* handle_offset */)};
|
| + ExpectRequestWithSharedMemoryHandles(kId, expected_requests,
|
| + &shared_memory_handles);
|
| + sink_.ClearMessages();
|
| +
|
| + // Populate the shared memory.
|
| + EXPECT_EQ(1u, shared_memory_handles.size());
|
| + EXPECT_TRUE(base::SharedMemory::IsHandleValid(shared_memory_handles[0]));
|
| + {
|
| + base::SharedMemory memory(
|
| + base::SharedMemory::DuplicateHandle(shared_memory_handles[0]), false);
|
| + memory.Map(kTestBlobStorageMaxSharedMemoryBytes);
|
| + std::memset(memory.memory(), 'Z', kTestBlobStorageMaxSharedMemoryBytes);
|
| + memory.Close();
|
| + }
|
| + // Send the confirmation.
|
| + responses = {BlobItemBytesResponse(1)};
|
| + host_->OnMemoryItemResponse(kId, responses);
|
| +
|
| + ExpectDone(kId);
|
| + sink_.ClearMessages();
|
| + base::RunLoop().RunUntilIdle();
|
| + EXPECT_TRUE(built);
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + scoped_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
|
| + EXPECT_TRUE(handle);
|
| +
|
| + DataElement expected;
|
| + expected.SetToAllocatedBytes(kLargeSize / 2);
|
| + std::memset(expected.mutable_bytes(), 'X', kLargeSize / 2);
|
| + elements = {expected};
|
| + std::memset(expected.mutable_bytes(), 'Z', kLargeSize / 2);
|
| + elements.push_back(expected);
|
| + ExpectHandleEqualsData(handle.get(), elements);
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, OnCancelBuildingBlob) {
|
| + const std::string kId("id");
|
| + // We ignore blobs that are unknown, as it could have been cancelled earlier
|
| + // and the renderer didn't know about it yet.
|
| + host_->OnCancelBuildingBlob(kId, IPCBlobCreationCancelCode::UNKNOWN);
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| +
|
| + // Start building blob.
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| + DataElement element;
|
| + element.SetToBytesDescription(kDataSize);
|
| + std::vector<DataElement> elements = {element};
|
| + host_->OnStartBuildingBlob(kId, elements);
|
| + // It should have requested memory here.
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| + sink_.ClearMessages();
|
| +
|
| + // Cancel in middle of construction.
|
| + host_->OnCancelBuildingBlob(kId, IPCBlobCreationCancelCode::UNKNOWN);
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + EXPECT_TRUE(host_->IsInUseInHost(kId));
|
| + EXPECT_FALSE(IsBeingBuiltInHost(kId));
|
| + // Check that's it's broken.
|
| + scoped_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
|
| + EXPECT_TRUE(handle->IsBroken());
|
| + handle.reset();
|
| + base::RunLoop().RunUntilIdle();
|
| +
|
| + // Get rid of it in the host.
|
| + host_->OnDecrementBlobRefCount(kId);
|
| + ExpectBlobNotExist(kId);
|
| +
|
| + // Create blob again to verify we don't have any old construction state lying
|
| + // around.
|
| + AsyncBlobTransfer(kId);
|
| +
|
| + // Check data.
|
| + handle = context_->GetBlobDataFromUUID(kId);
|
| + EXPECT_TRUE(handle);
|
| + DataElement expected;
|
| + expected.SetToBytes(kData, kDataSize);
|
| + std::vector<DataElement> expecteds = {expected};
|
| + ExpectHandleEqualsData(handle.get(), expecteds);
|
| +
|
| + // Verify we can't cancel after the fact.
|
| + host_->OnCancelBuildingBlob(kId, IPCBlobCreationCancelCode::UNKNOWN);
|
| + ExpectAndResetBadMessage();
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, BlobDataWithHostDeletion) {
|
| + // Build up a basic blob.
|
| + const std::string kId("id");
|
| + AsyncShortcutBlobTransfer(kId);
|
| + scoped_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
|
| + EXPECT_TRUE(handle);
|
| +
|
| + // Kill the host.
|
| + host_ = nullptr;
|
| + base::RunLoop().RunUntilIdle();
|
| + // Should still be there due to the handle.
|
| + scoped_ptr<BlobDataHandle> another_handle =
|
| + context_->GetBlobDataFromUUID(kId);
|
| + EXPECT_TRUE(another_handle);
|
| +
|
| + // Should disappear after dropping both handles.
|
| + handle.reset();
|
| + another_handle.reset();
|
| + base::RunLoop().RunUntilIdle();
|
| +
|
| + handle = context_->GetBlobDataFromUUID(kId);
|
| + EXPECT_FALSE(handle);
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, BlobReferenceWhileConstructing) {
|
| + const std::string kId("id");
|
| +
|
| + // Start building blob.
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| +
|
| + // Grab the handle.
|
| + scoped_ptr<BlobDataHandle> blob_data_handle =
|
| + context_->GetBlobDataFromUUID(kId);
|
| + EXPECT_TRUE(blob_data_handle);
|
| + EXPECT_TRUE(blob_data_handle->IsBeingBuilt());
|
| + bool built = false;
|
| + blob_data_handle->RunOnConstructionComplete(
|
| + base::Bind(&SetPointerValue<bool>, &built));
|
| +
|
| + // Continue building.
|
| + DataElement element;
|
| + element.SetToBytesDescription(kDataSize);
|
| + std::vector<DataElement> elements = {element};
|
| + host_->OnStartBuildingBlob(kId, elements);
|
| + sink_.ClearMessages();
|
| +
|
| + // Send data.
|
| + BlobItemBytesResponse response(0);
|
| + std::memcpy(response.allocate_mutable_data(kDataSize), kData, kDataSize);
|
| + std::vector<BlobItemBytesResponse> responses = {response};
|
| + sink_.ClearMessages();
|
| + host_->OnMemoryItemResponse(kId, responses);
|
| +
|
| + ExpectDone(kId);
|
| + base::RunLoop().RunUntilIdle();
|
| + EXPECT_TRUE(built);
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, BlobReferenceWhileShortcutConstructing) {
|
| + const std::string kId("id");
|
| +
|
| + // Start building blob.
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| +
|
| + // Grab the handle.
|
| + scoped_ptr<BlobDataHandle> blob_data_handle =
|
| + context_->GetBlobDataFromUUID(kId);
|
| + EXPECT_TRUE(blob_data_handle);
|
| + EXPECT_TRUE(blob_data_handle->IsBeingBuilt());
|
| + bool built = false;
|
| + blob_data_handle->RunOnConstructionComplete(
|
| + base::Bind(&SetPointerValue<bool>, &built));
|
| +
|
| + // Continue building.
|
| + DataElement element;
|
| + element.SetToBytes(kData, kDataSize);
|
| + std::vector<DataElement> elements = {element};
|
| + host_->OnStartBuildingBlob(kId, elements);
|
| + ExpectDone(kId);
|
| + base::RunLoop().RunUntilIdle();
|
| + EXPECT_TRUE(built);
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, BlobReferenceWhileConstructingCancelled) {
|
| + const std::string kId("id");
|
| +
|
| + // Start building blob.
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| +
|
| + // Grab the handle.
|
| + scoped_ptr<BlobDataHandle> blob_data_handle =
|
| + context_->GetBlobDataFromUUID(kId);
|
| + EXPECT_TRUE(blob_data_handle);
|
| + EXPECT_TRUE(blob_data_handle->IsBeingBuilt());
|
| + bool built = true;
|
| + blob_data_handle->RunOnConstructionComplete(
|
| + base::Bind(&SetPointerValue<bool>, &built));
|
| +
|
| + // Cancel in middle of construction.
|
| + host_->OnCancelBuildingBlob(kId, IPCBlobCreationCancelCode::UNKNOWN);
|
| + base::RunLoop().RunUntilIdle();
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + EXPECT_TRUE(host_->IsInUseInHost(kId));
|
| + EXPECT_FALSE(IsBeingBuiltInHost(kId));
|
| + EXPECT_TRUE(blob_data_handle->IsBroken());
|
| + EXPECT_FALSE(built);
|
| + built = true;
|
| + blob_data_handle->RunOnConstructionComplete(
|
| + base::Bind(&SetPointerValue<bool>, &built));
|
| + EXPECT_FALSE(built);
|
| +
|
| + // Remove it.
|
| + blob_data_handle.reset();
|
| + base::RunLoop().RunUntilIdle();
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + host_->OnDecrementBlobRefCount(kId);
|
| + ExpectBlobNotExist(kId);
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, DecrementRefAfterRegister) {
|
| + const std::string kId("id");
|
| + // Decrement the refcount while building (renderer blob gc'd before
|
| + // construction was completed).
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + host_->OnDecrementBlobRefCount(kId);
|
| + EXPECT_FALSE(context_->registry().HasEntry(kId));
|
| + EXPECT_FALSE(IsBeingBuiltInHost(kId));
|
| + ExpectCancel(kId,
|
| + IPCBlobCreationCancelCode::BLOB_DEREFERENCED_WHILE_BUILDING);
|
| + sink_.ClearMessages();
|
| +
|
| + // Do the same, but this time grab a handle before we decrement.
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| + scoped_ptr<BlobDataHandle> blob_data_handle =
|
| + context_->GetBlobDataFromUUID(kId);
|
| + host_->OnDecrementBlobRefCount(kId);
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + EXPECT_TRUE(IsBeingBuiltInHost(kId));
|
| +
|
| + // Finish up the blob, and verify we got the done message.
|
| + DataElement element;
|
| + element.SetToBytes(kData, kDataSize);
|
| + std::vector<DataElement> elements = {element};
|
| + host_->OnStartBuildingBlob(kId, elements);
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| + ExpectDone(kId);
|
| + sink_.ClearMessages();
|
| + // Get rid of the handle, and verify it's gone.
|
| + blob_data_handle.reset();
|
| + base::RunLoop().RunUntilIdle();
|
| + // Check that it's no longer around.
|
| + EXPECT_FALSE(context_->registry().HasEntry(kId));
|
| + EXPECT_FALSE(IsBeingBuiltInHost(kId));
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, DecrementRefAfterOnStart) {
|
| + const std::string kId("id");
|
| +
|
| + // Decrement the refcount while building, after we call OnStartBuildlingBlob.
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| + DataElement element;
|
| + element.SetToBytesDescription(kDataSize);
|
| + std::vector<DataElement> elements = {element};
|
| + host_->OnStartBuildingBlob(kId, elements);
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| +
|
| + std::vector<BlobItemBytesRequest> expected_requests = {
|
| + BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize)};
|
| + ExpectRequest(kId, expected_requests);
|
| + sink_.ClearMessages();
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + host_->OnDecrementBlobRefCount(kId);
|
| + EXPECT_FALSE(context_->registry().HasEntry(kId));
|
| + EXPECT_FALSE(IsBeingBuiltInHost(kId));
|
| + ExpectCancel(kId,
|
| + IPCBlobCreationCancelCode::BLOB_DEREFERENCED_WHILE_BUILDING);
|
| + sink_.ClearMessages();
|
| +
|
| + // Do the same, but this time grab a handle to keep it alive.
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| + host_->OnStartBuildingBlob(kId, elements);
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| + ExpectRequest(kId, expected_requests);
|
| + sink_.ClearMessages();
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + // Grab the handle before decrementing.
|
| + scoped_ptr<BlobDataHandle> blob_data_handle =
|
| + context_->GetBlobDataFromUUID(kId);
|
| + host_->OnDecrementBlobRefCount(kId);
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + EXPECT_TRUE(IsBeingBuiltInHost(kId));
|
| +
|
| + // We finish the blob, and verify that we send 'Done' back to the renderer.
|
| + BlobItemBytesResponse response(0);
|
| + std::memcpy(response.allocate_mutable_data(kDataSize), kData, kDataSize);
|
| + std::vector<BlobItemBytesResponse> responses = {response};
|
| + host_->OnMemoryItemResponse(kId, responses);
|
| + ExpectDone(kId);
|
| + sink_.ClearMessages();
|
| + // Check that it's still around.
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + EXPECT_FALSE(IsBeingBuiltInHost(kId));
|
| +
|
| + // Get rid of the handle, and verify it's gone.
|
| + blob_data_handle.reset();
|
| + base::RunLoop().RunUntilIdle();
|
| + // Check that it's no longer around.
|
| + EXPECT_FALSE(context_->registry().HasEntry(kId));
|
| + EXPECT_FALSE(IsBeingBuiltInHost(kId));
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, DecrementRefAfterOnStartWithHandle) {
|
| + const std::string kId("id");
|
| + // Case 3: Decrement the refcount while building, after we call
|
| + // OnStartBuildlingBlob, except we have another handle.
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| +
|
| + scoped_ptr<BlobDataHandle> blob_data_handle =
|
| + context_->GetBlobDataFromUUID(kId);
|
| + EXPECT_TRUE(blob_data_handle->IsBeingBuilt());
|
| + bool built = true;
|
| + blob_data_handle->RunOnConstructionComplete(
|
| + base::Bind(&SetPointerValue<bool>, &built));
|
| +
|
| + DataElement element;
|
| + element.SetToBytesDescription(kDataSize);
|
| + std::vector<DataElement> elements = {element};
|
| + host_->OnStartBuildingBlob(kId, elements);
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| +
|
| + // Check that we got the expected request.
|
| + std::vector<BlobItemBytesRequest> expected_requests = {
|
| + BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize)};
|
| + ExpectRequest(kId, expected_requests);
|
| + sink_.ClearMessages();
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + EXPECT_TRUE(IsBeingBuiltInHost(kId));
|
| + // Decrement, simulating where the ref goes out of scope in renderer.
|
| + host_->OnDecrementBlobRefCount(kId);
|
| + // We still have the blob as it's not done.
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + EXPECT_TRUE(blob_data_handle->IsBeingBuilt());
|
| + EXPECT_TRUE(IsBeingBuiltInHost(kId));
|
| + // Cancel to clean up.
|
| + host_->OnCancelBuildingBlob(kId, IPCBlobCreationCancelCode::UNKNOWN);
|
| + // Run loop to propagate the handle decrement in the host.
|
| + base::RunLoop().RunUntilIdle();
|
| + // We still have the entry because of our earlier handle.
|
| + EXPECT_TRUE(context_->registry().HasEntry(kId));
|
| + EXPECT_FALSE(IsBeingBuiltInHost(kId));
|
| + sink_.ClearMessages();
|
| +
|
| + // Should disappear after dropping the handle.
|
| + EXPECT_TRUE(blob_data_handle->IsBroken());
|
| + blob_data_handle.reset();
|
| + base::RunLoop().RunUntilIdle();
|
| + EXPECT_FALSE(context_->registry().HasEntry(kId));
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, HostDisconnectAfterRegisterWithHandle) {
|
| + const std::string kId("id");
|
| +
|
| + // Delete host with a handle to the blob.
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| +
|
| + scoped_ptr<BlobDataHandle> blob_data_handle =
|
| + context_->GetBlobDataFromUUID(kId);
|
| + EXPECT_TRUE(blob_data_handle->IsBeingBuilt());
|
| + bool built = true;
|
| + blob_data_handle->RunOnConstructionComplete(
|
| + base::Bind(&SetPointerValue<bool>, &built));
|
| + // Get rid of host, which was doing the constructing.
|
| + host_ = nullptr;
|
| + EXPECT_FALSE(blob_data_handle->IsBeingBuilt());
|
| + base::RunLoop().RunUntilIdle();
|
| + EXPECT_FALSE(built);
|
| +
|
| + // Should still be there due to the handle.
|
| + scoped_ptr<BlobDataHandle> another_handle =
|
| + context_->GetBlobDataFromUUID(kId);
|
| + EXPECT_TRUE(another_handle);
|
| +
|
| + // Should disappear after dropping both handles.
|
| + blob_data_handle.reset();
|
| + another_handle.reset();
|
| + base::RunLoop().RunUntilIdle();
|
| + EXPECT_FALSE(context_->registry().HasEntry(kId));
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, HostDisconnectAfterOnStart) {
|
| + const std::string kId("id");
|
| +
|
| + // Host deleted after OnStartBuilding.
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| +
|
| + DataElement element;
|
| + element.SetToBytesDescription(kDataSize);
|
| + std::vector<DataElement> elements = {element};
|
| + host_->OnStartBuildingBlob(kId, elements);
|
| +
|
| + std::vector<BlobItemBytesRequest> expected_requests = {
|
| + BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize)};
|
| + ExpectRequest(kId, expected_requests);
|
| + sink_.ClearMessages();
|
| + host_ = nullptr;
|
| + // We need to run the message loop because of the handle in the async builder.
|
| + base::RunLoop().RunUntilIdle();
|
| + EXPECT_FALSE(context_->registry().HasEntry(kId));
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, HostDisconnectAfterOnMemoryResponse) {
|
| + const std::string kId("id");
|
| +
|
| + // Host deleted after OnMemoryItemResponse.
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| +
|
| + // Create list of two items.
|
| + DataElement element;
|
| + element.SetToBytesDescription(kDataSize);
|
| + std::vector<DataElement> elements = {element, element};
|
| + host_->OnStartBuildingBlob(kId, elements);
|
| + std::vector<BlobItemBytesRequest> expected_requests = {
|
| + BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize),
|
| + BlobItemBytesRequest::CreateIPCRequest(1, 1, 0, kDataSize)};
|
| + ExpectRequest(kId, expected_requests);
|
| + sink_.ClearMessages();
|
| +
|
| + // Send just one response so the blob isn't 'done' yet.
|
| + BlobItemBytesResponse response(0);
|
| + std::memcpy(response.allocate_mutable_data(kDataSize), kData, kDataSize);
|
| + std::vector<BlobItemBytesResponse> responses = {response};
|
| + host_->OnMemoryItemResponse(kId, responses);
|
| + EXPECT_EQ(0u, sink_.message_count());
|
| +
|
| + host_ = nullptr;
|
| + base::RunLoop().RunUntilIdle();
|
| + EXPECT_FALSE(context_->registry().HasEntry(kId));
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, CreateBlobWithBrokenReference) {
|
| + const std::string kBrokenId("id1");
|
| + const std::string kReferencingId("id2");
|
| +
|
| + // First, let's test a circular reference.
|
| + const std::string kCircularId("id1");
|
| + host_->OnRegisterBlobUUID(kCircularId, std::string(kContentType),
|
| + std::string(kContentDisposition), {kCircularId});
|
| + ExpectAndResetBadMessage();
|
| +
|
| + // Next, test a blob that references a broken blob.
|
| + host_->OnRegisterBlobUUID(kBrokenId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| + host_->OnCancelBuildingBlob(kBrokenId, IPCBlobCreationCancelCode::UNKNOWN);
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| + EXPECT_TRUE(context_->GetBlobDataFromUUID(kBrokenId)->IsBroken());
|
| +
|
| + // Create referencing blob. We should be broken right away, but also ignore
|
| + // the subsequent OnStart message.
|
| + host_->OnRegisterBlobUUID(kReferencingId, std::string(kContentType),
|
| + std::string(kContentDisposition), {kBrokenId});
|
| + EXPECT_TRUE(context_->GetBlobDataFromUUID(kReferencingId)->IsBroken());
|
| + EXPECT_FALSE(IsBeingBuiltInHost(kReferencingId));
|
| + EXPECT_TRUE(context_->registry().HasEntry(kReferencingId));
|
| + ExpectCancel(kReferencingId,
|
| + IPCBlobCreationCancelCode::REFERENCED_BLOB_BROKEN);
|
| + sink_.ClearMessages();
|
| +
|
| + DataElement element;
|
| + element.SetToBytesDescription(kDataSize);
|
| + std::vector<DataElement> elements = {element};
|
| + element.SetToBlob(kBrokenId);
|
| + elements.push_back(element);
|
| + host_->OnStartBuildingBlob(kReferencingId, elements);
|
| + EXPECT_EQ(0u, sink_.message_count());
|
| + base::RunLoop().RunUntilIdle();
|
| +}
|
| +
|
| +TEST_F(BlobDispatcherHostTest, DeferenceBlobOnDifferentHost) {
|
| + const std::string kId("id");
|
| + // Data elements for our transfer & checking messages.
|
| + DataElement element;
|
| + element.SetToBytesDescription(kDataSize);
|
| + std::vector<DataElement> elements = {element};
|
| + std::vector<BlobItemBytesRequest> expected_requests = {
|
| + BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize)};
|
| + BlobItemBytesResponse response(0);
|
| + std::memcpy(response.allocate_mutable_data(kDataSize), kData, kDataSize);
|
| + std::vector<BlobItemBytesResponse> responses = {response};
|
| +
|
| + scoped_refptr<TestableBlobDispatcherHost> host2(
|
| + new TestableBlobDispatcherHost(chrome_blob_storage_context_, &sink_));
|
| +
|
| + // Delete host with another host having a referencing, then dereference on
|
| + // second host. Verify we're still building it on first host, and then
|
| + // verify that a building message from the renderer will kill it.
|
| +
|
| + // Test OnStartBuilding after double dereference.
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| + host2->OnIncrementBlobRefCount(kId);
|
| + host_->OnDecrementBlobRefCount(kId);
|
| + EXPECT_FALSE(host_->IsInUseInHost(kId));
|
| + host2->OnDecrementBlobRefCount(kId);
|
| + // So no more blob in the context, but we're still being built in host 1.
|
| + EXPECT_FALSE(context_->registry().HasEntry(kId));
|
| + EXPECT_TRUE(host_->async_builder_.IsBeingBuilt(kId));
|
| + host_->OnStartBuildingBlob(kId, elements);
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| + // We should be cleaned up.
|
| + EXPECT_FALSE(host_->async_builder_.IsBeingBuilt(kId));
|
| + ExpectCancel(kId,
|
| + IPCBlobCreationCancelCode::BLOB_DEREFERENCED_WHILE_BUILDING);
|
| + sink_.ClearMessages();
|
| +
|
| + // Same as above, but test OnMemoryItemResponse after double dereference.
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| + host2->OnIncrementBlobRefCount(kId);
|
| + host_->OnDecrementBlobRefCount(kId);
|
| + EXPECT_FALSE(host_->IsInUseInHost(kId));
|
| + host_->OnStartBuildingBlob(kId, elements);
|
| + ExpectRequest(kId, expected_requests);
|
| + sink_.ClearMessages();
|
| +
|
| + host2->OnDecrementBlobRefCount(kId);
|
| + // So no more blob in the context, but we're still being built in host 1.
|
| + EXPECT_FALSE(context_->registry().HasEntry(kId));
|
| + EXPECT_TRUE(host_->async_builder_.IsBeingBuilt(kId));
|
| + host_->OnMemoryItemResponse(kId, responses);
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| + // We should be cleaned up.
|
| + EXPECT_FALSE(host_->async_builder_.IsBeingBuilt(kId));
|
| + ExpectCancel(kId,
|
| + IPCBlobCreationCancelCode::BLOB_DEREFERENCED_WHILE_BUILDING);
|
| + sink_.ClearMessages();
|
| +
|
| + // Same, but now for OnCancel.
|
| + host_->OnRegisterBlobUUID(kId, std::string(kContentType),
|
| + std::string(kContentDisposition),
|
| + std::set<std::string>());
|
| + host2->OnIncrementBlobRefCount(kId);
|
| + host_->OnDecrementBlobRefCount(kId);
|
| + EXPECT_FALSE(host_->IsInUseInHost(kId));
|
| + host_->OnStartBuildingBlob(kId, elements);
|
| + ExpectRequest(kId, expected_requests);
|
| + sink_.ClearMessages();
|
| +
|
| + host2->OnDecrementBlobRefCount(kId);
|
| + // So no more blob in the context, but we're still being built in host 1.
|
| + EXPECT_FALSE(context_->registry().HasEntry(kId));
|
| + EXPECT_TRUE(host_->async_builder_.IsBeingBuilt(kId));
|
| + host_->OnCancelBuildingBlob(kId, IPCBlobCreationCancelCode::UNKNOWN);
|
| + EXPECT_FALSE(host_->shutdown_for_bad_message_);
|
| + // We should be cleaned up.
|
| + EXPECT_FALSE(host_->async_builder_.IsBeingBuilt(kId));
|
| +}
|
| +
|
| +} // namespace content
|
|
|