| // 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 <stddef.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/shared_memory.h" |
| #include "base/run_loop.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "storage/browser/blob/blob_data_builder.h" |
| #include "storage/browser/blob/blob_data_handle.h" |
| #include "storage/browser/blob/blob_storage_context.h" |
| #include "storage/browser/blob/blob_transport_host.h" |
| #include "storage/common/blob_storage/blob_storage_constants.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace content { |
| namespace { |
| const std::string kBlobUUID = "blobUUIDYAY"; |
| const std::string kContentType = "content_type"; |
| const std::string kContentDisposition = "content_disposition"; |
| const std::string kCompletedBlobUUID = "completedBlob"; |
| const std::string kCompletedBlobData = "completedBlobData"; |
| |
| const size_t kTestBlobStorageIPCThresholdBytes = 5; |
| const size_t kTestBlobStorageMaxSharedMemoryBytes = 20; |
| |
| const size_t kTestBlobStorageMaxBlobMemorySize = 400; |
| const uint64_t kTestBlobStorageMaxDiskSpace = 4000; |
| const uint64_t kTestBlobStorageMinFileSizeBytes = 10; |
| const uint64_t kTestBlobStorageMaxFileSizeBytes = 100; |
| |
| void PopulateBytes(char* bytes, size_t length) { |
| for (size_t i = 0; i < length; i++) { |
| bytes[i] = static_cast<char>(i); |
| } |
| } |
| |
| void AddMemoryItem(size_t length, std::vector<network::DataElement>* out) { |
| network::DataElement bytes; |
| bytes.SetToBytesDescription(length); |
| out->push_back(std::move(bytes)); |
| } |
| |
| void AddShortcutMemoryItem(size_t length, |
| std::vector<network::DataElement>* out) { |
| network::DataElement bytes; |
| bytes.SetToAllocatedBytes(length); |
| PopulateBytes(bytes.mutable_bytes(), length); |
| out->push_back(std::move(bytes)); |
| } |
| |
| void AddShortcutMemoryItem(size_t length, storage::BlobDataBuilder* out) { |
| network::DataElement bytes; |
| bytes.SetToAllocatedBytes(length); |
| PopulateBytes(bytes.mutable_bytes(), length); |
| out->AppendData(bytes.bytes(), length); |
| } |
| |
| void AddBlobItem(std::vector<network::DataElement>* out) { |
| network::DataElement blob; |
| blob.SetToBlob(kCompletedBlobUUID); |
| out->push_back(std::move(blob)); |
| } |
| } // namespace |
| |
| class BlobTransportHostTest : public testing::Test { |
| public: |
| BlobTransportHostTest() |
| : status_code_(storage::BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS), |
| request_called_(false) {} |
| ~BlobTransportHostTest() override {} |
| |
| void SetUp() override { |
| status_code_ = storage::BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; |
| request_called_ = false; |
| requests_.clear(); |
| memory_handles_.clear(); |
| storage::BlobStorageLimits limits; |
| limits.max_ipc_memory_size = kTestBlobStorageIPCThresholdBytes; |
| limits.max_shared_memory_size = kTestBlobStorageMaxSharedMemoryBytes; |
| limits.max_blob_in_memory_space = kTestBlobStorageMaxBlobMemorySize; |
| limits.desired_max_disk_space = kTestBlobStorageMaxDiskSpace; |
| limits.effective_max_disk_space = kTestBlobStorageMaxDiskSpace; |
| limits.min_page_file_size = kTestBlobStorageMinFileSizeBytes; |
| limits.max_file_size = kTestBlobStorageMaxFileSizeBytes; |
| context_.mutable_memory_controller()->set_limits_for_testing(limits); |
| storage::BlobDataBuilder builder(kCompletedBlobUUID); |
| builder.AppendData(kCompletedBlobData); |
| completed_blob_handle_ = context_.AddFinishedBlob(builder); |
| EXPECT_EQ(storage::BlobStatus::DONE, |
| completed_blob_handle_->GetBlobStatus()); |
| } |
| |
| void StatusCallback(storage::BlobStatus status) { |
| status_called_ = true; |
| status_code_ = status; |
| } |
| |
| void RequestMemoryCallback( |
| std::vector<storage::BlobItemBytesRequest> requests, |
| std::vector<base::SharedMemoryHandle> shared_memory_handles, |
| std::vector<base::File> files) { |
| requests_ = std::move(requests); |
| memory_handles_ = std::move(shared_memory_handles); |
| request_called_ = true; |
| } |
| |
| storage::BlobStatus BuildBlobAsync( |
| const std::string& uuid, |
| const std::vector<network::DataElement>& descriptions, |
| std::unique_ptr<storage::BlobDataHandle>* storage) { |
| EXPECT_NE(storage, nullptr); |
| request_called_ = false; |
| status_called_ = false; |
| *storage = host_.StartBuildingBlob( |
| uuid, kContentType, kContentDisposition, descriptions, &context_, |
| nullptr, |
| base::Bind(&BlobTransportHostTest::RequestMemoryCallback, |
| base::Unretained(this)), |
| base::Bind(&BlobTransportHostTest::StatusCallback, |
| base::Unretained(this))); |
| if (status_called_) |
| return status_code_; |
| else |
| return context_.GetBlobStatus(uuid); |
| } |
| |
| storage::BlobStatus GetBlobStatus(const std::string& uuid) const { |
| return context_.GetBlobStatus(uuid); |
| } |
| |
| bool IsBeingBuiltInContext(const std::string& uuid) const { |
| return BlobStatusIsPending(context_.GetBlobStatus(uuid)); |
| } |
| |
| TestBrowserThreadBundle browser_thread_bundle_; |
| storage::BlobStorageContext context_; |
| storage::BlobTransportHost host_; |
| bool status_called_; |
| storage::BlobStatus status_code_; |
| |
| bool request_called_; |
| std::vector<storage::BlobItemBytesRequest> requests_; |
| std::vector<base::SharedMemoryHandle> memory_handles_; |
| std::unique_ptr<storage::BlobDataHandle> completed_blob_handle_; |
| }; |
| |
| // The 'shortcut' method is when the data is included in the initial IPCs and |
| // the browser uses that instead of requesting the memory. |
| TEST_F(BlobTransportHostTest, TestShortcut) { |
| std::vector<network::DataElement> descriptions; |
| |
| AddShortcutMemoryItem(10, &descriptions); |
| AddBlobItem(&descriptions); |
| AddShortcutMemoryItem(300, &descriptions); |
| |
| storage::BlobDataBuilder expected(kBlobUUID); |
| expected.set_content_type(kContentType); |
| expected.set_content_disposition(kContentDisposition); |
| AddShortcutMemoryItem(10, &expected); |
| expected.AppendData(kCompletedBlobData); |
| AddShortcutMemoryItem(300, &expected); |
| |
| std::unique_ptr<storage::BlobDataHandle> handle; |
| EXPECT_EQ(storage::BlobStatus::DONE, |
| BuildBlobAsync(kBlobUUID, descriptions, &handle)); |
| |
| EXPECT_FALSE(request_called_); |
| EXPECT_EQ(0u, host_.blob_building_count()); |
| EXPECT_FALSE(handle->IsBeingBuilt()); |
| ASSERT_FALSE(handle->IsBroken()); |
| std::unique_ptr<storage::BlobDataSnapshot> data = handle->CreateSnapshot(); |
| EXPECT_EQ(expected, *data); |
| data.reset(); |
| handle.reset(); |
| base::RunLoop().RunUntilIdle(); |
| }; |
| |
| TEST_F(BlobTransportHostTest, TestShortcutNoRoom) { |
| std::vector<network::DataElement> descriptions; |
| |
| AddShortcutMemoryItem(10, &descriptions); |
| AddBlobItem(&descriptions); |
| AddShortcutMemoryItem(5000, &descriptions); |
| |
| std::unique_ptr<storage::BlobDataHandle> handle; |
| EXPECT_EQ(storage::BlobStatus::ERR_OUT_OF_MEMORY, |
| BuildBlobAsync(kBlobUUID, descriptions, &handle)); |
| |
| EXPECT_FALSE(request_called_); |
| EXPECT_EQ(0u, host_.blob_building_count()); |
| }; |
| |
| TEST_F(BlobTransportHostTest, TestSingleSharedMemRequest) { |
| std::vector<network::DataElement> descriptions; |
| const size_t kSize = kTestBlobStorageIPCThresholdBytes + 1; |
| AddMemoryItem(kSize, &descriptions); |
| |
| std::unique_ptr<storage::BlobDataHandle> handle; |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, |
| BuildBlobAsync(kBlobUUID, descriptions, &handle)); |
| EXPECT_TRUE(handle); |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, handle->GetBlobStatus()); |
| |
| EXPECT_TRUE(request_called_); |
| EXPECT_EQ(1u, host_.blob_building_count()); |
| ASSERT_EQ(1u, requests_.size()); |
| request_called_ = false; |
| |
| EXPECT_EQ(storage::BlobItemBytesRequest::CreateSharedMemoryRequest( |
| 0, 0, 0, kSize, 0, 0), |
| requests_.at(0)); |
| }; |
| |
| TEST_F(BlobTransportHostTest, TestMultipleSharedMemRequests) { |
| std::vector<network::DataElement> descriptions; |
| const size_t kSize = kTestBlobStorageMaxSharedMemoryBytes + 1; |
| const char kFirstBlockByte = 7; |
| const char kSecondBlockByte = 19; |
| AddMemoryItem(kSize, &descriptions); |
| |
| storage::BlobDataBuilder expected(kBlobUUID); |
| expected.set_content_type(kContentType); |
| expected.set_content_disposition(kContentDisposition); |
| char data[kSize]; |
| memset(data, kFirstBlockByte, kTestBlobStorageMaxSharedMemoryBytes); |
| expected.AppendData(data, kTestBlobStorageMaxSharedMemoryBytes); |
| expected.AppendData(&kSecondBlockByte, 1); |
| |
| std::unique_ptr<storage::BlobDataHandle> handle; |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, |
| BuildBlobAsync(kBlobUUID, descriptions, &handle)); |
| |
| EXPECT_TRUE(request_called_); |
| EXPECT_EQ(1u, host_.blob_building_count()); |
| ASSERT_EQ(1u, requests_.size()); |
| request_called_ = false; |
| |
| // We need to grab a duplicate handle so we can have two blocks open at the |
| // same time. |
| base::SharedMemoryHandle shared_mem_handle = |
| base::SharedMemory::DuplicateHandle(memory_handles_.at(0)); |
| EXPECT_TRUE(base::SharedMemory::IsHandleValid(shared_mem_handle)); |
| base::SharedMemory shared_memory(shared_mem_handle, false); |
| EXPECT_TRUE(shared_memory.Map(kTestBlobStorageMaxSharedMemoryBytes)); |
| |
| EXPECT_EQ(storage::BlobItemBytesRequest::CreateSharedMemoryRequest( |
| 0, 0, 0, kTestBlobStorageMaxSharedMemoryBytes, 0, 0), |
| requests_.at(0)); |
| |
| memset(shared_memory.memory(), kFirstBlockByte, |
| kTestBlobStorageMaxSharedMemoryBytes); |
| |
| storage::BlobItemBytesResponse response(0); |
| std::vector<storage::BlobItemBytesResponse> responses = {response}; |
| host_.OnMemoryResponses(kBlobUUID, responses, &context_); |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, GetBlobStatus(kBlobUUID)); |
| ASSERT_TRUE(handle); |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, handle->GetBlobStatus()); |
| |
| EXPECT_TRUE(request_called_); |
| EXPECT_EQ(1u, host_.blob_building_count()); |
| ASSERT_EQ(1u, requests_.size()); |
| request_called_ = false; |
| |
| EXPECT_EQ(storage::BlobItemBytesRequest::CreateSharedMemoryRequest( |
| 1, 0, kTestBlobStorageMaxSharedMemoryBytes, 1, 0, 0), |
| requests_.at(0)); |
| |
| memset(shared_memory.memory(), kSecondBlockByte, 1); |
| |
| response.request_number = 1; |
| responses[0] = response; |
| host_.OnMemoryResponses(kBlobUUID, responses, &context_); |
| EXPECT_TRUE(handle); |
| EXPECT_EQ(storage::BlobStatus::DONE, handle->GetBlobStatus()); |
| EXPECT_FALSE(request_called_); |
| EXPECT_EQ(0u, host_.blob_building_count()); |
| std::unique_ptr<storage::BlobDataHandle> blob_handle = |
| context_.GetBlobDataFromUUID(kBlobUUID); |
| EXPECT_FALSE(blob_handle->IsBeingBuilt()); |
| EXPECT_FALSE(blob_handle->IsBroken()); |
| std::unique_ptr<storage::BlobDataSnapshot> blob_data = |
| blob_handle->CreateSnapshot(); |
| EXPECT_EQ(expected, *blob_data); |
| }; |
| |
| TEST_F(BlobTransportHostTest, TestBasicIPCAndStopBuilding) { |
| std::vector<network::DataElement> descriptions; |
| |
| AddMemoryItem(2, &descriptions); |
| AddBlobItem(&descriptions); |
| AddMemoryItem(2, &descriptions); |
| |
| storage::BlobDataBuilder expected(kBlobUUID); |
| expected.set_content_type(kContentType); |
| expected.set_content_disposition(kContentDisposition); |
| AddShortcutMemoryItem(2, &expected); |
| expected.AppendData(kCompletedBlobData); |
| AddShortcutMemoryItem(2, &expected); |
| |
| std::unique_ptr<storage::BlobDataHandle> handle1; |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, |
| BuildBlobAsync(kBlobUUID, descriptions, &handle1)); |
| EXPECT_TRUE(handle1); |
| host_.CancelBuildingBlob(kBlobUUID, storage::BlobStatus::ERR_OUT_OF_MEMORY, |
| &context_); |
| |
| // Check that we're broken, and then remove the blob. |
| EXPECT_FALSE(handle1->IsBeingBuilt()); |
| EXPECT_TRUE(handle1->IsBroken()); |
| handle1.reset(); |
| base::RunLoop().RunUntilIdle(); |
| handle1 = context_.GetBlobDataFromUUID(kBlobUUID); |
| EXPECT_FALSE(handle1.get()); |
| |
| // This should succeed because we've removed all references to the blob. |
| std::unique_ptr<storage::BlobDataHandle> handle2; |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, |
| BuildBlobAsync(kBlobUUID, descriptions, &handle2)); |
| |
| EXPECT_TRUE(request_called_); |
| EXPECT_EQ(1u, host_.blob_building_count()); |
| request_called_ = false; |
| |
| storage::BlobItemBytesResponse response1(0); |
| PopulateBytes(response1.allocate_mutable_data(2), 2); |
| storage::BlobItemBytesResponse response2(1); |
| PopulateBytes(response2.allocate_mutable_data(2), 2); |
| std::vector<storage::BlobItemBytesResponse> responses = {response1, |
| response2}; |
| |
| host_.OnMemoryResponses(kBlobUUID, responses, &context_); |
| EXPECT_EQ(storage::BlobStatus::DONE, handle2->GetBlobStatus()); |
| EXPECT_FALSE(request_called_); |
| EXPECT_EQ(0u, host_.blob_building_count()); |
| EXPECT_FALSE(handle2->IsBeingBuilt()); |
| EXPECT_FALSE(handle2->IsBroken()); |
| std::unique_ptr<storage::BlobDataSnapshot> blob_data = |
| handle2->CreateSnapshot(); |
| EXPECT_EQ(expected, *blob_data); |
| }; |
| |
| TEST_F(BlobTransportHostTest, TestBreakingAllBuilding) { |
| const std::string& kBlob1 = "blob1"; |
| const std::string& kBlob2 = "blob2"; |
| const std::string& kBlob3 = "blob3"; |
| |
| std::vector<network::DataElement> descriptions; |
| AddMemoryItem(2, &descriptions); |
| |
| // Register blobs. |
| std::unique_ptr<storage::BlobDataHandle> handle1; |
| std::unique_ptr<storage::BlobDataHandle> handle2; |
| std::unique_ptr<storage::BlobDataHandle> handle3; |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, |
| BuildBlobAsync(kBlob1, descriptions, &handle1)); |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, |
| BuildBlobAsync(kBlob2, descriptions, &handle2)); |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, |
| BuildBlobAsync(kBlob3, descriptions, &handle3)); |
| |
| EXPECT_TRUE(request_called_); |
| EXPECT_TRUE(handle1->IsBeingBuilt() && handle2->IsBeingBuilt() && |
| handle3->IsBeingBuilt()); |
| EXPECT_FALSE(handle1->IsBroken() || handle2->IsBroken() || |
| handle3->IsBroken()); |
| |
| EXPECT_TRUE(IsBeingBuiltInContext(kBlob1) && IsBeingBuiltInContext(kBlob2) && |
| IsBeingBuiltInContext(kBlob3)); |
| |
| // This shouldn't call the transport complete callbacks, so our handles should |
| // still be false. |
| host_.CancelAll(&context_); |
| |
| EXPECT_FALSE(handle1->IsBeingBuilt() || handle2->IsBeingBuilt() || |
| handle3->IsBeingBuilt()); |
| EXPECT_TRUE(handle1->IsBroken() && handle2->IsBroken() && |
| handle3->IsBroken()); |
| |
| base::RunLoop().RunUntilIdle(); |
| }; |
| |
| TEST_F(BlobTransportHostTest, TestBadIPCs) { |
| std::vector<network::DataElement> descriptions; |
| |
| // Test reusing same blob uuid. |
| AddMemoryItem(10, &descriptions); |
| AddBlobItem(&descriptions); |
| AddMemoryItem(300, &descriptions); |
| std::unique_ptr<storage::BlobDataHandle> handle1; |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, |
| BuildBlobAsync(kBlobUUID, descriptions, &handle1)); |
| EXPECT_TRUE(host_.IsBeingBuilt(kBlobUUID)); |
| EXPECT_TRUE(request_called_); |
| host_.CancelBuildingBlob( |
| kBlobUUID, storage::BlobStatus::ERR_REFERENCED_BLOB_BROKEN, &context_); |
| handle1.reset(); |
| EXPECT_FALSE(context_.GetBlobDataFromUUID(kBlobUUID).get()); |
| |
| // Test empty responses. |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, |
| BuildBlobAsync(kBlobUUID, descriptions, &handle1)); |
| std::vector<storage::BlobItemBytesResponse> responses; |
| host_.OnMemoryResponses(kBlobUUID, responses, &context_); |
| EXPECT_EQ(storage::BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS, |
| handle1->GetBlobStatus()); |
| handle1.reset(); |
| |
| // Test response problems below here. |
| descriptions.clear(); |
| AddMemoryItem(2, &descriptions); |
| AddBlobItem(&descriptions); |
| AddMemoryItem(2, &descriptions); |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, |
| BuildBlobAsync(kBlobUUID, descriptions, &handle1)); |
| |
| // Invalid request number. |
| storage::BlobItemBytesResponse response1(3); |
| PopulateBytes(response1.allocate_mutable_data(2), 2); |
| responses = {response1}; |
| host_.OnMemoryResponses(kBlobUUID, responses, &context_); |
| EXPECT_EQ(storage::BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS, |
| handle1->GetBlobStatus()); |
| EXPECT_TRUE(context_.GetBlobDataFromUUID(kBlobUUID)->IsBroken()); |
| handle1.reset(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Duplicate request number responses. |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, |
| BuildBlobAsync(kBlobUUID, descriptions, &handle1)); |
| response1.request_number = 0; |
| storage::BlobItemBytesResponse response2(0); |
| PopulateBytes(response2.allocate_mutable_data(2), 2); |
| responses = {response1, response2}; |
| host_.OnMemoryResponses(kBlobUUID, responses, &context_); |
| EXPECT_EQ(storage::BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS, |
| handle1->GetBlobStatus()); |
| EXPECT_TRUE(context_.GetBlobDataFromUUID(kBlobUUID)->IsBroken()); |
| handle1.reset(); |
| base::RunLoop().RunUntilIdle(); |
| }; |
| |
| TEST_F(BlobTransportHostTest, WaitOnReferencedBlob) { |
| const std::string& kBlob1 = "blob1"; |
| const std::string& kBlob2 = "blob2"; |
| const std::string& kBlob3 = "blob3"; |
| |
| std::vector<network::DataElement> descriptions; |
| AddMemoryItem(2, &descriptions); |
| |
| // Register blobs. |
| std::unique_ptr<storage::BlobDataHandle> handle1; |
| |
| std::unique_ptr<storage::BlobDataHandle> handle2; |
| std::unique_ptr<storage::BlobDataHandle> handle3; |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, |
| BuildBlobAsync(kBlob1, descriptions, &handle1)); |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, |
| BuildBlobAsync(kBlob2, descriptions, &handle2)); |
| EXPECT_TRUE(request_called_); |
| request_called_ = false; |
| |
| // Finish the third one, with a reference to the first and second blob. |
| network::DataElement element; |
| element.SetToBlob(kBlob1); |
| descriptions.push_back(std::move(element)); |
| element.SetToBlob(kBlob2); |
| descriptions.push_back(std::move(element)); |
| |
| EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, |
| BuildBlobAsync(kBlob3, descriptions, &handle3)); |
| EXPECT_TRUE(request_called_); |
| request_called_ = false; |
| |
| // Finish the third, but we should still be 'building' it. |
| storage::BlobItemBytesResponse response1(0); |
| PopulateBytes(response1.allocate_mutable_data(2), 2); |
| std::vector<storage::BlobItemBytesResponse> responses = {response1}; |
| host_.OnMemoryResponses(kBlob3, responses, &context_); |
| EXPECT_EQ(storage::BlobStatus::PENDING_REFERENCED_BLOBS, |
| handle3->GetBlobStatus()); |
| EXPECT_FALSE(request_called_); |
| EXPECT_FALSE(host_.IsBeingBuilt(kBlob3)); |
| EXPECT_TRUE(IsBeingBuiltInContext(kBlob3)); |
| |
| // Finish the first. |
| descriptions.clear(); |
| AddShortcutMemoryItem(2, &descriptions); |
| host_.OnMemoryResponses(kBlob1, responses, &context_); |
| EXPECT_EQ(storage::BlobStatus::DONE, handle1->GetBlobStatus()); |
| EXPECT_FALSE(request_called_); |
| EXPECT_FALSE(host_.IsBeingBuilt(kBlob1)); |
| EXPECT_FALSE(IsBeingBuiltInContext(kBlob1)); |
| EXPECT_TRUE(context_.GetBlobDataFromUUID(kBlob1)); |
| |
| // Run the message loop so we propogate the construction complete callbacks. |
| base::RunLoop().RunUntilIdle(); |
| // Verify we're not done. |
| EXPECT_TRUE(IsBeingBuiltInContext(kBlob3)); |
| |
| // Finish the second. |
| host_.OnMemoryResponses(kBlob2, responses, &context_); |
| EXPECT_EQ(storage::BlobStatus::DONE, handle2->GetBlobStatus()); |
| EXPECT_FALSE(request_called_); |
| EXPECT_FALSE(host_.IsBeingBuilt(kBlob2)); |
| EXPECT_FALSE(IsBeingBuiltInContext(kBlob2)); |
| EXPECT_TRUE(context_.GetBlobDataFromUUID(kBlob2)); |
| |
| // Run the message loop so we propogate the construction complete callbacks. |
| base::RunLoop().RunUntilIdle(); |
| // Finally, we should be finished with third blob. |
| EXPECT_FALSE(host_.IsBeingBuilt(kBlob3)); |
| EXPECT_FALSE(IsBeingBuiltInContext(kBlob3)); |
| EXPECT_TRUE(context_.GetBlobDataFromUUID(kBlob3)); |
| }; |
| |
| } // namespace content |