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

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

Issue 1568523002: Implement content::WebSocketBlobSender (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@websocket_blob_send_ipcs
Patch Set: Rebase. Created 4 years, 10 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_sender_unittest.cc
diff --git a/content/browser/renderer_host/websocket_blob_sender_unittest.cc b/content/browser/renderer_host/websocket_blob_sender_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..fe2f936d123a1a8372624473d03ac6256514c47b
--- /dev/null
+++ b/content/browser/renderer_host/websocket_blob_sender_unittest.cc
@@ -0,0 +1,446 @@
+// 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 "content/browser/renderer_host/websocket_blob_sender.h"
+
+#include <string.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/task_runner.h"
+#include "base/time/time.h"
+#include "content/browser/fileapi/chrome_blob_storage_context.h"
+#include "content/public/browser/blob_handle.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/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+const char kDummyUrl[] = "http://www.example.com/";
+const char kBanana[] = "banana";
+
+// This is small so that the tests do not waste too much time just copying bytes
+// around. But it has to be larger than kMinimumNonFinalFrameSize defined in
+// websocket_blob_sender.cc.
+const size_t kInitialQuota = 16 * 1024;
+
+using net::TestCompletionCallback;
+
+// A fake channel for testing. Records the contents of the message that was sent
+// through it. Quota is restricted, and is refreshed asynchronously in response
+// to calls to SendFrame().
+class FakeChannel : public WebSocketBlobSender::Channel {
+ public:
+ // |notify_new_quota| will be run asynchronously on the current MessageLoop
+ // every time GetSendQuota() increases.
+ FakeChannel() : weak_factory_(this) {}
+
+ // This method must be called before SendFrame() is.
+ void set_notify_new_quota(const base::Closure& notify_new_quota) {
+ notify_new_quota_ = notify_new_quota;
+ }
+
+ size_t GetSendQuota() const override { return current_send_quota_; }
+
+ ChannelState SendFrame(bool fin, const std::vector<char>& data) override {
+ ++frames_sent_;
+ EXPECT_FALSE(got_fin_);
+ if (fin)
+ got_fin_ = true;
+ EXPECT_LE(data.size(), current_send_quota_);
+ message_.insert(message_.end(), data.begin(), data.end());
+ current_send_quota_ -= data.size();
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeChannel::RefreshQuota, weak_factory_.GetWeakPtr()));
+ return net::WebSocketEventInterface::CHANNEL_ALIVE;
+ }
+
+ bool got_fin() const { return got_fin_; }
+
+ int frames_sent() const { return frames_sent_; }
+
+ const std::vector<char>& message() const { return message_; }
+
+ private:
+ void RefreshQuota() {
+ if (current_send_quota_ == kInitialQuota)
+ return;
+ current_send_quota_ = kInitialQuota;
+ DCHECK(!notify_new_quota_.is_null());
+ notify_new_quota_.Run();
+ }
+
+ base::Closure notify_new_quota_;
+ size_t current_send_quota_ = kInitialQuota;
+ int frames_sent_ = 0;
+ bool got_fin_ = false;
+ std::vector<char> message_;
+ base::WeakPtrFactory<FakeChannel> weak_factory_;
+};
+
+class WebSocketBlobSenderTest : public ::testing::Test {
+ protected:
+ // The Windows implementation of net::FileStream::Context requires a real IO
+ // MessageLoop.
+ WebSocketBlobSenderTest()
+ : threads_(TestBrowserThreadBundle::IO_MAINLOOP),
+ chrome_blob_storage_context_(
+ ChromeBlobStorageContext::GetFor(&browser_context_)),
+ fake_channel_(nullptr),
+ sender_() {}
+ ~WebSocketBlobSenderTest() override {}
+
+ void SetUp() override {
+ // ChromeBlobStorageContext::GetFor() does some work asynchronously.
+ base::RunLoop().RunUntilIdle();
+ SetUpSender();
+ }
+
+ // This method can be overriden to use a different channel implementation.
+ virtual void SetUpSender() {
+ fake_channel_ = new FakeChannel;
+ sender_.reset(new WebSocketBlobSender(make_scoped_ptr(fake_channel_)));
+ fake_channel_->set_notify_new_quota(base::Bind(
+ &WebSocketBlobSender::OnNewSendQuota, base::Unretained(sender_.get())));
+ }
+
+ storage::BlobStorageContext* context() {
+ return chrome_blob_storage_context_->context();
+ }
+
+ storage::FileSystemContext* GetFileSystemContext() {
+ StoragePartition* partition = BrowserContext::GetStoragePartitionForSite(
+ &browser_context_, GURL(kDummyUrl));
+ return partition->GetFileSystemContext();
+ }
+
+ // |string| is copied.
+ scoped_ptr<BlobHandle> CreateMemoryBackedBlob(const char* string) {
+ scoped_ptr<BlobHandle> handle =
+ chrome_blob_storage_context_->CreateMemoryBackedBlob(string,
+ strlen(string));
+ EXPECT_TRUE(handle);
+ return handle;
+ }
+
+ // Call sender_.Start() with the other parameters filled in appropriately for
+ // this test fixture.
+ int Start(const std::string& uuid,
+ uint64_t expected_size,
+ const net::CompletionCallback& callback) {
+ net::WebSocketEventInterface::ChannelState channel_state =
+ net::WebSocketEventInterface::CHANNEL_ALIVE;
+ return sender_->Start(
+ uuid, expected_size, context(), GetFileSystemContext(),
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get(),
+ &channel_state, callback);
+ }
+
+ void NotCalledCallbackImpl(int rv) {
+ ADD_FAILURE()
+ << "Callback that should not be called was called with argument " << rv;
+ }
+
+ net::CompletionCallback NotCalled() {
+ return base::Bind(&WebSocketBlobSenderTest::NotCalledCallbackImpl,
+ base::Unretained(this));
+ }
+
+ void ExpectOkAndQuit(base::RunLoop* run_loop, int result) {
+ EXPECT_EQ(net::OK, result);
+ run_loop->Quit();
+ }
+
+ net::CompletionCallback ExpectOkAndQuitCallback(base::RunLoop* run_loop) {
+ return base::Bind(&WebSocketBlobSenderTest::ExpectOkAndQuit,
+ base::Unretained(this), run_loop);
+ }
+
+ TestBrowserThreadBundle threads_;
+ TestBrowserContext browser_context_;
+ scoped_refptr<ChromeBlobStorageContext> chrome_blob_storage_context_;
+ // |fake_channel_| is owned by |sender_|.
+ FakeChannel* fake_channel_;
+ scoped_ptr<WebSocketBlobSender> sender_;
+};
+
+TEST_F(WebSocketBlobSenderTest, Construction) {}
+
+TEST_F(WebSocketBlobSenderTest, EmptyBlob) {
+ scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob("");
+
+ // The APIs allow for this to be asynchronous but that is unlikely in
+ // practice.
+ int result = Start(handle->GetUUID(), UINT64_C(0), NotCalled());
+ // If this fails with result == -1, someone has changed the code to be
+ // asynchronous and this test should be adapted to match.
+ EXPECT_EQ(net::OK, result);
+ EXPECT_TRUE(fake_channel_->got_fin());
+ EXPECT_EQ(0U, fake_channel_->message().size());
+}
+
+TEST_F(WebSocketBlobSenderTest, SmallBlob) {
+ scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob(kBanana);
+
+ EXPECT_EQ(net::OK, Start(handle->GetUUID(), UINT64_C(6), NotCalled()));
+ EXPECT_TRUE(fake_channel_->got_fin());
+ EXPECT_EQ(1, fake_channel_->frames_sent());
+ EXPECT_EQ(std::vector<char>(kBanana, kBanana + 6), fake_channel_->message());
+}
+
+TEST_F(WebSocketBlobSenderTest, SizeMismatch) {
+ scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob(kBanana);
+
+ EXPECT_EQ(net::ERR_UPLOAD_FILE_CHANGED,
+ Start(handle->GetUUID(), UINT64_C(5), NotCalled()));
+ EXPECT_EQ(0, fake_channel_->frames_sent());
+}
+
+TEST_F(WebSocketBlobSenderTest, InvalidUUID) {
+ EXPECT_EQ(net::ERR_INVALID_HANDLE,
+ Start("sandwich", UINT64_C(0), NotCalled()));
+}
+
+TEST_F(WebSocketBlobSenderTest, LargeMessage) {
+ std::string message(kInitialQuota + 10, 'a');
+ scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob(message.c_str());
+
+ base::RunLoop run_loop;
+ int rv = Start(handle->GetUUID(), message.size(),
+ ExpectOkAndQuitCallback(&run_loop));
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+ EXPECT_EQ(1, fake_channel_->frames_sent());
+ run_loop.Run();
+ EXPECT_EQ(2, fake_channel_->frames_sent());
+ EXPECT_TRUE(fake_channel_->got_fin());
+ std::vector<char> expected_message(message.begin(), message.end());
+ EXPECT_EQ(expected_message, fake_channel_->message());
+}
+
+// A message exactly equal to the available quota should be sent in one frame.
+TEST_F(WebSocketBlobSenderTest, ExactSizeMessage) {
+ std::string message(kInitialQuota, 'a');
+ scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob(message.c_str());
+
+ EXPECT_EQ(net::OK, Start(handle->GetUUID(), message.size(), NotCalled()));
+ EXPECT_EQ(1, fake_channel_->frames_sent());
+ EXPECT_TRUE(fake_channel_->got_fin());
+ std::vector<char> expected_message(message.begin(), message.end());
+ EXPECT_EQ(expected_message, fake_channel_->message());
+}
+
+// If the connection is closed while sending a message, the WebSocketBlobSender
+// object will be destroyed. It needs to handle this case without error.
+TEST_F(WebSocketBlobSenderTest, AbortedSend) {
+ std::string message(kInitialQuota + 10, 'a');
+ scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob(message.c_str());
+
+ int rv = Start(handle->GetUUID(), message.size(), NotCalled());
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+ sender_.reset();
+}
+
+// Invalid file-backed blob.
+TEST_F(WebSocketBlobSenderTest, InvalidFileBackedBlob) {
+ base::FilePath path(FILE_PATH_LITERAL(
+ "WebSocketBlobSentTest.InvalidFileBackedBlob.NonExistentFile"));
+ scoped_ptr<BlobHandle> handle =
+ chrome_blob_storage_context_->CreateFileBackedBlob(path, 0u, 32u,
+ base::Time::Now());
+ EXPECT_TRUE(handle);
+
+ TestCompletionCallback callback;
+ int rv =
+ callback.GetResult(Start(handle->GetUUID(), 5u, callback.callback()));
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, rv);
+}
+
+// A test fixture that does the additional work necessary to create working
+// file-backed blobs.
+class WebSocketFileBackedBlobSenderTest : public WebSocketBlobSenderTest {
+ protected:
+ void SetUp() override {
+ WebSocketBlobSenderTest::SetUp();
+ // temp_dir_ is recursively deleted on destruction.
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ }
+
+ void CreateFile(const std::string& contents,
+ const base::FilePath& path,
+ base::File::Info* info) {
+ ASSERT_EQ(contents.size(), static_cast<size_t>(base::WriteFile(
+ path, contents.data(), contents.size())));
+ ASSERT_TRUE(base::GetFileInfo(path, info));
+ }
+
+ scoped_ptr<BlobHandle> CreateFileBackedBlob(const std::string& contents) {
+ base::FilePath path = temp_dir_.path().AppendASCII("blob.dat");
+ base::File::Info info;
+ CreateFile(contents, path, &info);
+ if (HasFatalFailure())
+ return nullptr;
+ return chrome_blob_storage_context_->CreateFileBackedBlob(
+ path, 0u, contents.size(), info.last_modified);
+ }
+
+ base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(WebSocketFileBackedBlobSenderTest, EmptyBlob) {
+ scoped_ptr<BlobHandle> handle = CreateFileBackedBlob("");
+ ASSERT_TRUE(handle);
+
+ TestCompletionCallback callback;
+ int result = callback.GetResult(
+ Start(handle->GetUUID(), UINT64_C(0), callback.callback()));
+ EXPECT_EQ(net::OK, result);
+ EXPECT_TRUE(fake_channel_->got_fin());
+ EXPECT_EQ(0U, fake_channel_->message().size());
+}
+
+TEST_F(WebSocketFileBackedBlobSenderTest, SizeMismatch) {
+ scoped_ptr<BlobHandle> handle = CreateFileBackedBlob(kBanana);
+ ASSERT_TRUE(handle);
+
+ TestCompletionCallback callback;
+ int result = Start(handle->GetUUID(), UINT64_C(8), callback.callback());
+ // This test explicitly aims to test the asynchronous code path, otherwise it
+ // would be identical to the other SizeMismatch test above.
+ EXPECT_EQ(net::ERR_IO_PENDING, result);
+ EXPECT_EQ(net::ERR_UPLOAD_FILE_CHANGED, callback.WaitForResult());
+ EXPECT_EQ(0, fake_channel_->frames_sent());
+}
+
+TEST_F(WebSocketFileBackedBlobSenderTest, LargeMessage) {
+ std::string message = "the green potato had lunch with the angry cat. ";
+ while (message.size() <= kInitialQuota) {
+ message = message + message;
+ }
+ scoped_ptr<BlobHandle> handle = CreateFileBackedBlob(message);
+ ASSERT_TRUE(handle);
+
+ TestCompletionCallback callback;
+ int result = Start(handle->GetUUID(), message.size(), callback.callback());
+ EXPECT_EQ(net::OK, callback.GetResult(result));
+ std::vector<char> expected_message(message.begin(), message.end());
+ EXPECT_EQ(expected_message, fake_channel_->message());
+}
+
+// The WebSocketBlobSender needs to handle a connection close while doing file
+// IO cleanly.
+TEST_F(WebSocketFileBackedBlobSenderTest, Aborted) {
+ scoped_ptr<BlobHandle> handle = CreateFileBackedBlob(kBanana);
+
+ int rv = Start(handle->GetUUID(), UINT64_C(6), NotCalled());
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+ sender_.reset();
+}
+
+class DeletingFakeChannel : public WebSocketBlobSender::Channel {
+ public:
+ explicit DeletingFakeChannel(
+ scoped_ptr<WebSocketBlobSender>* sender_to_delete)
+ : sender_(sender_to_delete) {}
+
+ size_t GetSendQuota() const override { return kInitialQuota; }
+
+ ChannelState SendFrame(bool fin, const std::vector<char>& data) override {
+ sender_->reset();
+ // |this| is deleted here.
+ return net::WebSocketEventInterface::CHANNEL_DELETED;
+ }
+
+ private:
+ scoped_ptr<WebSocketBlobSender>* sender_;
+};
+
+class WebSocketBlobSenderDeletingTest : public WebSocketBlobSenderTest {
+ protected:
+ void SetUpSender() override {
+ sender_.reset(new WebSocketBlobSender(
+ make_scoped_ptr(new DeletingFakeChannel(&sender_))));
+ }
+};
+
+// This test only does something useful when run under AddressSanitizer or a
+// similar tool that can detect use-after-free bugs.
+TEST_F(WebSocketBlobSenderDeletingTest, SenderDeleted) {
+ scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob(kBanana);
+
+ EXPECT_EQ(net::ERR_CONNECTION_RESET,
+ Start(handle->GetUUID(), UINT64_C(6), NotCalled()));
+ EXPECT_FALSE(sender_);
+}
+
+// SendFrame() calls OnSendNewQuota() synchronously while filling the operating
+// system's socket write buffer. The purpose of this Channel implementation is
+// to verify that the synchronous case works correctly.
+class SynchronousFakeChannel : public WebSocketBlobSender::Channel {
+ public:
+ // This method must be called before SendFrame() is.
+ void set_notify_new_quota(const base::Closure& notify_new_quota) {
+ notify_new_quota_ = notify_new_quota;
+ }
+
+ size_t GetSendQuota() const override { return kInitialQuota; }
+
+ ChannelState SendFrame(bool fin, const std::vector<char>& data) override {
+ message_.insert(message_.end(), data.begin(), data.end());
+ notify_new_quota_.Run();
+ return net::WebSocketEventInterface::CHANNEL_ALIVE;
+ }
+
+ const std::vector<char>& message() const { return message_; }
+
+ private:
+ base::Closure notify_new_quota_;
+ std::vector<char> message_;
+};
+
+class WebSocketBlobSenderSynchronousTest : public WebSocketBlobSenderTest {
+ protected:
+ void SetUpSender() override {
+ synchronous_fake_channel_ = new SynchronousFakeChannel;
+ sender_.reset(
+ new WebSocketBlobSender(make_scoped_ptr(synchronous_fake_channel_)));
+ synchronous_fake_channel_->set_notify_new_quota(base::Bind(
+ &WebSocketBlobSender::OnNewSendQuota, base::Unretained(sender_.get())));
+ }
+
+ SynchronousFakeChannel* synchronous_fake_channel_ = nullptr;
+};
+
+TEST_F(WebSocketBlobSenderSynchronousTest, LargeMessage) {
+ std::string message(kInitialQuota + 10, 'a');
+ scoped_ptr<BlobHandle> handle = CreateMemoryBackedBlob(message.c_str());
+
+ int rv = Start(handle->GetUUID(), message.size(), NotCalled());
+ EXPECT_EQ(net::OK, rv);
+ std::vector<char> expected_message(message.begin(), message.end());
+ EXPECT_EQ(expected_message, synchronous_fake_channel_->message());
+}
+
+} // namespace
+
+} // namespace content

Powered by Google App Engine
This is Rietveld 408576698