blob: 8db82856c1121cd8695a4795eca58e739b2d8ace [file] [log] [blame]
// Copyright 2019 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/websocket/web_socket_impl.h"
#include "cobalt/websocket/web_socket.h"
#include <memory>
#include <vector>
#include "base/memory/ref_counted.h"
#include "base/test/scoped_task_environment.h"
#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/dom/dom_exception.h"
#include "cobalt/dom/dom_settings.h"
#include "cobalt/dom/window.h"
#include "cobalt/network/network_module.h"
#include "cobalt/script/script_exception.h"
#include "cobalt/script/testing/mock_exception_state.h"
#include "cobalt/websocket/mock_websocket_channel.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::SaveArg;
using ::testing::StrictMock;
using ::testing::DefaultValue;
using ::testing::Return;
using cobalt::script::testing::MockExceptionState;
namespace cobalt {
namespace websocket {
namespace {
// These limits are copied from net::WebSocketChannel implementation.
const int kDefaultSendQuotaHighWaterMark = 1 << 17;
const int k800KB = 800;
const int kTooMuch = kDefaultSendQuotaHighWaterMark + 1;
const int kWayTooMuch = kDefaultSendQuotaHighWaterMark * 2 + 1;
const int k512KB = 512;
class FakeSettings : public dom::DOMSettings {
public:
FakeSettings()
: dom::DOMSettings(0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
null_debugger_hooks_, NULL),
base_("https://127.0.0.1:1234") {
network_module_.reset(new network::NetworkModule());
this->set_network_module(network_module_.get());
}
const GURL& base_url() const override { return base_; }
// public members, so that they're easier for testing.
base::NullDebuggerHooks null_debugger_hooks_;
GURL base_;
std::unique_ptr<network::NetworkModule> network_module_;
};
} // namespace
class WebSocketImplTest : public ::testing::Test {
public:
dom::DOMSettings* settings() const { return settings_.get(); }
void AddQuota(int quota) {
network_task_runner_->PostBlockingTask(
FROM_HERE,
base::Bind(&WebSocketImpl::OnFlowControl, websocket_impl_, quota));
}
protected:
WebSocketImplTest() : settings_(new FakeSettings()) {
std::vector<std::string> sub_protocols;
sub_protocols.push_back("chat");
network_task_runner_ = settings_->network_module()
->url_request_context_getter()
->GetNetworkTaskRunner();
}
void SetUp() override {
websocket_impl_ = new WebSocketImpl(settings_->network_module(), nullptr);
// Setting this was usually done by WebSocketImpl::Connect, but since we do
// not do Connect for every test, we have to make sure its task runner is
// set.
websocket_impl_->delegate_task_runner_ = network_task_runner_;
// The holder is only created to be base::Passed() on the next line, it will
// be empty so do not use it later.
network_task_runner_->PostBlockingTask(
FROM_HERE,
base::Bind(
[](scoped_refptr<WebSocketImpl> websocket_impl,
MockWebSocketChannel** mock_channel_slot,
dom::DOMSettings* settings) {
*mock_channel_slot = new MockWebSocketChannel(
websocket_impl.get(), settings->network_module());
websocket_impl->websocket_channel_ =
std::unique_ptr<net::WebSocketChannel>(*mock_channel_slot);
},
websocket_impl_, &mock_channel_, settings()));
}
void TearDown() override {
network_task_runner_->PostBlockingTask(
FROM_HERE,
base::Bind(&WebSocketImpl::OnClose, websocket_impl_, true /*was_clan*/,
net::kWebSocketNormalClosure /*error_code*/,
"" /*close_reason*/));
}
base::test::ScopedTaskEnvironment env_;
std::unique_ptr<FakeSettings> settings_;
scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
scoped_refptr<WebSocketImpl> websocket_impl_;
MockWebSocketChannel* mock_channel_;
StrictMock<MockExceptionState> exception_state_;
};
TEST_F(WebSocketImplTest, NormalSizeRequest) {
// Normally the high watermark quota is given at websocket connection success.
AddQuota(kDefaultSendQuotaHighWaterMark);
{
base::AutoLock scoped_lock(mock_channel_->lock());
// mock_channel_ is created and used on network thread.
EXPECT_CALL(
*mock_channel_,
MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeText, _, k800KB))
.Times(1)
.WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
}
char data[k800KB];
int32 buffered_amount = 0;
std::string error;
websocket_impl_->SendText(data, k800KB, &buffered_amount, &error);
}
TEST_F(WebSocketImplTest, LargeRequest) {
AddQuota(kDefaultSendQuotaHighWaterMark);
// mock_channel_ is created and used on network thread.
{
base::AutoLock scoped_lock(mock_channel_->lock());
EXPECT_CALL(*mock_channel_,
MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeText, _,
kDefaultSendQuotaHighWaterMark))
.Times(1)
.WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
}
char data[kDefaultSendQuotaHighWaterMark];
int32 buffered_amount = 0;
std::string error;
websocket_impl_->SendText(data, kDefaultSendQuotaHighWaterMark,
&buffered_amount, &error);
}
TEST_F(WebSocketImplTest, OverLimitRequest) {
AddQuota(kDefaultSendQuotaHighWaterMark);
// mock_channel_ is created and used on network thread.
{
base::AutoLock scoped_lock(mock_channel_->lock());
EXPECT_CALL(*mock_channel_,
MockSendFrame(false, net::WebSocketFrameHeader::kOpCodeText, _,
kDefaultSendQuotaHighWaterMark))
.Times(2)
.WillRepeatedly(Return(net::WebSocketChannel::CHANNEL_ALIVE));
EXPECT_CALL(
*mock_channel_,
MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeText, _, 1))
.Times(1)
.WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
}
char data[kWayTooMuch];
int32 buffered_amount = 0;
std::string error;
websocket_impl_->SendText(data, kWayTooMuch, &buffered_amount, &error);
AddQuota(kDefaultSendQuotaHighWaterMark);
AddQuota(kDefaultSendQuotaHighWaterMark);
}
TEST_F(WebSocketImplTest, ReuseSocketForLargeRequest) {
AddQuota(kDefaultSendQuotaHighWaterMark);
// mock_channel_ is created and used on network thread.
{
base::AutoLock scoped_lock(mock_channel_->lock());
EXPECT_CALL(*mock_channel_,
MockSendFrame(false, net::WebSocketFrameHeader::kOpCodeBinary,
_, kDefaultSendQuotaHighWaterMark))
.Times(1)
.WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
EXPECT_CALL(
*mock_channel_,
MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeBinary, _, 1))
.Times(1)
.WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
EXPECT_CALL(*mock_channel_,
MockSendFrame(false, net::WebSocketFrameHeader::kOpCodeText, _,
k512KB - 1))
.Times(1)
.WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
EXPECT_CALL(*mock_channel_,
MockSendFrame(true, net::WebSocketFrameHeader::kOpCodeText, _,
kTooMuch - (k512KB - 1)))
.Times(1)
.WillOnce(Return(net::WebSocketChannel::CHANNEL_ALIVE));
}
char data[kTooMuch];
int32 buffered_amount = 0;
std::string error;
websocket_impl_->SendBinary(data, kTooMuch, &buffered_amount, &error);
websocket_impl_->SendText(data, kTooMuch, &buffered_amount, &error);
AddQuota(k512KB);
AddQuota(kDefaultSendQuotaHighWaterMark);
}
} // namespace websocket
} // namespace cobalt