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

Unified Diff: content/browser/renderer_host/websocket_blob_receiver_unittest.cc

Issue 1626453003: [OBSOLETE] Browser-side implementation of WebSocket Blob receive. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Add WebSocketHost::ReceiveQuotaMultiplexer Created 4 years, 11 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/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
« no previous file with comments | « content/browser/renderer_host/websocket_blob_receiver.cc ('k') | content/browser/renderer_host/websocket_host.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698