| 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
|
|
|