Index: content/browser/renderer_host/websocket_blob_receiver_unittest.cc |
diff --git a/content/browser/renderer_host/websocket_blob_receiver_unittest.cc b/content/browser/renderer_host/websocket_blob_receiver_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..90a7f44962bb7f036917d8c702a40914b9adc57b |
--- /dev/null |
+++ b/content/browser/renderer_host/websocket_blob_receiver_unittest.cc |
@@ -0,0 +1,351 @@ |
+// Copyright 2016 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 "websocket_blob_receiver.h" |
+ |
+#include <string> |
+ |
+#include "base/run_loop.h" |
+#include "base/strings/string_piece.h" |
+#include "content/browser/fileapi/chrome_blob_storage_context.h" |
+#include "content/public/browser/browser_context.h" |
+#include "content/public/browser/browser_thread.h" |
+#include "content/public/browser/storage_partition.h" |
+#include "content/public/test/test_browser_context.h" |
+#include "content/public/test/test_browser_thread_bundle.h" |
+#include "net/base/completion_callback.h" |
+#include "net/base/io_buffer.h" |
+#include "net/base/net_errors.h" |
+#include "net/base/test_completion_callback.h" |
+#include "storage/browser/blob/blob_data_handle.h" |
+#include "storage/browser/blob/blob_reader.h" |
+#include "storage/browser/fileapi/file_system_context.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "url/gurl.h" |
+ |
+namespace content { |
+ |
+namespace { |
+ |
+using base::RunLoop; |
+using base::WeakPtr; |
+using net::TestCompletionCallback; |
+using storage::BlobDataHandle; |
+ |
+class FakeClient : public WebSocketBlobReceiver::Client { |
+ public: |
+ FakeClient() : weak_factory_(this) {} |
+ |
+ void BlobCreated(const BlobDataHandle& blob_data_handle) override { |
+ blob_data_handle_.reset(new BlobDataHandle(blob_data_handle)); |
+ done_run_loop_.Quit(); |
+ } |
+ |
+ void BlobFailed(int net_error_code) override { |
+ failure_code_ = net_error_code; |
+ done_run_loop_.Quit(); |
+ } |
+ |
+ void AddFlowControlQuota(int64_t quota) override { |
+ total_quota_ += quota; |
+ if (flow_control_run_loop_) |
+ flow_control_run_loop_->Quit(); |
+ } |
+ |
+ WeakPtr<FakeClient> GetWeakPtr() { return weak_factory_.GetWeakPtr(); } |
+ |
+ const BlobDataHandle* blob_data_handle() { return blob_data_handle_.get(); } |
+ |
+ int failure_code() { return failure_code_; } |
+ |
+ void WaitUntilDone() { done_run_loop_.Run(); } |
+ |
+ int64_t total_quota() { return total_quota_; } |
+ |
+ // The point of this class is that it starts waiting at the point when it is |
+ // initialised. This means Wait() will always return, even if the flow control |
+ // is supplied before Wait() is called. |
+ class FlowControlWaiter { |
+ public: |
+ FlowControlWaiter(WeakPtr<FakeClient> fake_client) |
+ : fake_client_(fake_client) { |
+ fake_client->flow_control_run_loop_ = &run_loop_; |
+ } |
+ |
+ ~FlowControlWaiter() { |
+ if (fake_client_) |
+ fake_client_->flow_control_run_loop_ = nullptr; |
+ } |
+ |
+ void Wait() { |
+ run_loop_.Run(); |
+ if (fake_client_) |
+ fake_client_->flow_control_run_loop_ = nullptr; |
+ } |
+ |
+ private: |
+ WeakPtr<FakeClient> fake_client_; |
+ RunLoop run_loop_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(FlowControlWaiter); |
+ }; |
+ |
+ private: |
+ RunLoop done_run_loop_; |
+ RunLoop* flow_control_run_loop_; |
+ scoped_ptr<BlobDataHandle> blob_data_handle_; |
+ int failure_code_ = net::OK; |
+ int64_t total_quota_ = 0; |
+ base::WeakPtrFactory<FakeClient> weak_factory_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(FakeClient); |
+}; |
+ |
+class WebSocketBlobReceiverTest : public ::testing::Test { |
+ protected: |
+ // The Windows implementation of net::FileStream::Context requires a real IO |
+ // MessageLoop. |
+ WebSocketBlobReceiverTest() |
+ : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), |
+ browser_context_(), |
+ chrome_blob_storage_context_( |
+ ChromeBlobStorageContext::GetFor(&browser_context_)) {} |
+ |
+ void SetUp() override { |
+ // Allow ChromeBlobStorageContext to initialise the BlobStorageContext on |
+ // the "IO thread". |
+ RunLoop().RunUntilIdle(); |
+ FakeClient* fake_client = new FakeClient; |
+ fake_client_ = fake_client->GetWeakPtr(); |
+ receiver_.reset(new WebSocketBlobReceiver( |
+ make_scoped_ptr(fake_client), chrome_blob_storage_context_->context())); |
+ } |
+ |
+ std::string BlobToString(const BlobDataHandle* blob_data_handle) { |
+ static const char kDummyUrl[] = "http://www.example.com/"; |
+ using storage::BlobReader; |
+ |
+ StoragePartition* partition = BrowserContext::GetStoragePartitionForSite( |
+ &browser_context_, GURL(kDummyUrl)); |
+ storage::FileSystemContext* file_system_context = |
+ partition->GetFileSystemContext(); |
+ scoped_ptr<BlobReader> blob_reader = blob_data_handle->CreateReader( |
+ file_system_context, |
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get()); |
+ |
+ TestCompletionCallback size_callback; |
+ switch (blob_reader->CalculateSize(size_callback.callback())) { |
+ case BlobReader::Status::DONE: |
+ break; |
+ |
+ case BlobReader::Status::IO_PENDING: |
+ EXPECT_EQ(net::OK, size_callback.WaitForResult()); |
+ break; |
+ |
+ case BlobReader::Status::NET_ERROR: |
+ ADD_FAILURE() << "BlobReader::CalculateSize returned error: " |
+ << net::ErrorToString(blob_reader->net_error()); |
+ break; |
+ } |
+ |
+ TestCompletionCallback read_callback; |
+ size_t dest_size = static_cast<size_t>(blob_reader->total_size()); |
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(dest_size)); |
+ int bytes_read = 0; |
+ switch (blob_reader->Read(buffer.get(), dest_size, &bytes_read, |
+ read_callback.callback())) { |
+ case BlobReader::Status::DONE: |
+ EXPECT_EQ(dest_size, static_cast<size_t>(bytes_read)); |
+ break; |
+ |
+ case BlobReader::Status::IO_PENDING: |
+ EXPECT_EQ(static_cast<int>(dest_size), read_callback.WaitForResult()); |
+ break; |
+ |
+ case BlobReader::Status::NET_ERROR: |
+ ADD_FAILURE() << "BlobReader::Read returned error: " |
+ << net::ErrorToString(blob_reader->net_error()); |
+ break; |
+ } |
+ |
+ return std::string(buffer->data(), buffer->data() + dest_size); |
+ } |
+ |
+ // Test for success, and provide a useful diagnostic if we don't have it. |
+ void ExpectSuccess() { |
+ EXPECT_EQ(net::OK, fake_client_->failure_code()) |
+ << net::ErrorToString(fake_client_->failure_code()); |
+ } |
+ |
+ // Start the WebSocketBlobReceiver, and wait for the temporary file to be |
+ // opened (indicated by quota being supplied). |
+ void StartAndWaitForOpen() { |
+ FakeClient::FlowControlWaiter await_open(fake_client_); |
+ receiver_->Start(); |
+ await_open.Wait(); |
+ } |
+ |
+ // Convert |data| to a vector and append it. Expect success. |data| must be |
+ // non-empty. |
+ void AppendDataSuccessfully(base::StringPiece data) { |
+ CHECK(!data.empty()); |
+ EXPECT_EQ(net::ERR_IO_PENDING, receiver_->AppendData(std::vector<char>( |
+ data.begin(), data.end()))); |
+ } |
+ |
+ // Finish, wait until completion, and expect success. |
+ void FinishAndWaitForSuccess() { |
+ EXPECT_EQ(net::ERR_IO_PENDING, receiver_->Finish()); |
+ fake_client_->WaitUntilDone(); |
+ ExpectSuccess(); |
+ } |
+ |
+ TestBrowserThreadBundle thread_bundle_; |
+ TestBrowserContext browser_context_; |
+ scoped_refptr<ChromeBlobStorageContext> chrome_blob_storage_context_; |
+ WeakPtr<FakeClient> fake_client_; |
+ scoped_ptr<WebSocketBlobReceiver> receiver_; |
+}; |
+ |
+TEST_F(WebSocketBlobReceiverTest, Construct) {} |
+ |
+TEST_F(WebSocketBlobReceiverTest, SendEmptyBlob) { |
+ receiver_->Start(); |
+ |
+ EXPECT_EQ(net::OK, receiver_->AppendData(std::vector<char>())); |
+ |
+ EXPECT_EQ(net::ERR_IO_PENDING, receiver_->Finish()); |
+ fake_client_->WaitUntilDone(); |
+ |
+ ExpectSuccess(); |
+ EXPECT_EQ("", BlobToString(fake_client_->blob_data_handle())); |
+} |
+ |
+TEST_F(WebSocketBlobReceiverTest, SendNonEmptyBlob) { |
+ receiver_->Start(); |
+ |
+ // This violates the interface contract since no quota has been provided yet, |
+ // but WebSocketBlobReceiver doesn't strictly enforce its quota invariants |
+ // since the WebSocket wire implementation does that. |
+ EXPECT_EQ(net::ERR_IO_PENDING, |
+ receiver_->AppendData(std::vector<char>(4, 'a'))); |
+ |
+ FinishAndWaitForSuccess(); |
+ |
+ EXPECT_EQ("aaaa", BlobToString(fake_client_->blob_data_handle())); |
+} |
+ |
+TEST_F(WebSocketBlobReceiverTest, SendAfterOpenComplete) { |
+ StartAndWaitForOpen(); |
+ |
+ EXPECT_GT(fake_client_->total_quota(), 0); |
+ |
+ AppendDataSuccessfully("bbbb"); |
+ |
+ FinishAndWaitForSuccess(); |
+ EXPECT_EQ("bbbb", BlobToString(fake_client_->blob_data_handle())); |
+} |
+ |
+TEST_F(WebSocketBlobReceiverTest, SendDuringWrite) { |
+ StartAndWaitForOpen(); |
+ |
+ AppendDataSuccessfully("cc"); |
+ AppendDataSuccessfully("dd"); |
+ |
+ FinishAndWaitForSuccess(); |
+ EXPECT_EQ("ccdd", BlobToString(fake_client_->blob_data_handle())); |
+} |
+ |
+TEST_F(WebSocketBlobReceiverTest, UsedQuotaIsReturned) { |
+ const size_t kBytesToUse = 8; |
+ StartAndWaitForOpen(); |
+ |
+ int64_t initial_quota = fake_client_->total_quota(); |
+ std::string data(kBytesToUse, 'e'); |
+ |
+ // It isn't actually guaranteed that all the quota will be returned in one |
+ // call, but this should work in practice. |
+ FakeClient::FlowControlWaiter await_more_quota(fake_client_); |
+ AppendDataSuccessfully(data); |
+ await_more_quota.Wait(); |
+ |
+ int64_t new_quota = fake_client_->total_quota() - initial_quota; |
+ EXPECT_EQ(kBytesToUse, static_cast<size_t>(new_quota)); |
+ |
+ FinishAndWaitForSuccess(); |
+} |
+ |
+TEST_F(WebSocketBlobReceiverTest, WaitForQuota) { |
+ StartAndWaitForOpen(); |
+ |
+ int64_t initial_quota = fake_client_->total_quota(); |
+ std::string data1(static_cast<size_t>(initial_quota), 'w'); |
+ |
+ FakeClient::FlowControlWaiter await_more_quota(fake_client_); |
+ AppendDataSuccessfully(data1); |
+ await_more_quota.Wait(); |
+ |
+ int64_t new_quota = fake_client_->total_quota() - initial_quota; |
+ std::string data2(static_cast<size_t>(new_quota), 'f'); |
+ AppendDataSuccessfully(data2); |
+ |
+ FinishAndWaitForSuccess(); |
+ EXPECT_EQ(data1 + data2, BlobToString(fake_client_->blob_data_handle())); |
+} |
+ |
+// Deleting the WebSocketBlobReceiver should cleanly abort all operations and |
+// leak no memory or temporary files. |
+TEST_F(WebSocketBlobReceiverTest, AbortAfterStart) { |
+ receiver_->Start(); |
+ receiver_.reset(); |
+} |
+ |
+TEST_F(WebSocketBlobReceiverTest, AbortAfterOpen) { |
+ StartAndWaitForOpen(); |
+ receiver_.reset(); |
+} |
+ |
+TEST_F(WebSocketBlobReceiverTest, AbortAfterAppend) { |
+ StartAndWaitForOpen(); |
+ AppendDataSuccessfully("banana"); |
+ receiver_.reset(); |
+} |
+ |
+TEST_F(WebSocketBlobReceiverTest, AbortDuringLargeWrite) { |
+ StartAndWaitForOpen(); |
+ int64_t initial_quota = fake_client_->total_quota(); |
+ receiver_->AppendData( |
+ std::vector<char>(static_cast<size_t>(initial_quota), 'g')); |
+ receiver_.reset(); |
+} |
+ |
+TEST_F(WebSocketBlobReceiverTest, AbortAfterSendQuota) { |
+ StartAndWaitForOpen(); |
+ FakeClient::FlowControlWaiter await_more_quota(fake_client_); |
+ AppendDataSuccessfully("orange"); |
+ await_more_quota.Wait(); |
+ receiver_.reset(); |
+} |
+ |
+TEST_F(WebSocketBlobReceiverTest, AbortAfterAppendAndFinish) { |
+ StartAndWaitForOpen(); |
+ AppendDataSuccessfully("apple"); |
+ receiver_->Finish(); |
+ receiver_.reset(); |
+} |
+ |
+// The difference of this test from AbortAfterAppendAndFinish is that it waits |
+// for the write to complete before calling Finish(). |
+TEST_F(WebSocketBlobReceiverTest, AbortDuringFinish) { |
+ StartAndWaitForOpen(); |
+ FakeClient::FlowControlWaiter await_more_quota(fake_client_); |
+ AppendDataSuccessfully("kiwi"); |
+ await_more_quota.Wait(); |
+ receiver_->Finish(); |
+ receiver_.reset(); |
+} |
+ |
+} // namespace |
+ |
+} // namespace content |