Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(129)

Unified Diff: content/browser/blob_storage/blob_dispatcher_host_unittest.cc

Issue 1234813004: [BlobAsync] Asynchronous Blob Construction Final Patch (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@blob-protocol-change
Patch Set: comments Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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_;
kinuko 2016/03/15 15:29:06 nit: sink_ could be probably private?
dmurph 2016/03/15 22:40:14 Done.
+
+ protected:
+ ~TestableBlobDispatcherHost() override {}
+
+ private:
+ friend class base::RefCountedThreadSafe<TestableBlobDispatcherHost>;
+};
+
+} // namespace
+
+class BlobDispatcherHostTest : public testing::Test {
+ protected:
+ BlobDispatcherHostTest()
+ : browser_thread_bundle_(),
+ browser_context_(),
kinuko 2016/03/15 15:29:06 nit: do we need these initializers here / can't th
dmurph 2016/03/15 22:40:14 ooops, I had args here, removing them now.
+ 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
kinuko 2016/03/15 15:29:06 nit: what are case 1 and 2? (Though I could assum
dmurph 2016/03/15 22:40:14 Yes, the other tests we cases 1 and 2, they were a
+ // 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

Powered by Google App Engine
This is Rietveld 408576698