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 |