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

Unified Diff: net/websockets/websocket_channel_test.cc

Issue 12764006: WebSocketChannel implementation (Closed) Base URL: http://git.chromium.org/chromium/src.git@web_socket_dispatcher
Patch Set: Add unit tests and fix bugs. Created 7 years, 6 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: net/websockets/websocket_channel_test.cc
diff --git a/net/websockets/websocket_channel_test.cc b/net/websockets/websocket_channel_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..dd856b4aee5ce4027252ef4d8c0eb907f93feb0f
--- /dev/null
+++ b/net/websockets/websocket_channel_test.cc
@@ -0,0 +1,833 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
yhirano 2013/06/26 11:33:29 I think websocket_channel_unittest.cc would be a b
Adam Rice 2013/06/27 17:29:49 The style guide says "_unittest and _regtest are d
yhirano 2013/06/28 06:49:17 I see, thank you!
tyoshino (SeeGerritForStatus) 2013/06/28 07:55:27 I don't have strong opinion. Both make sense. Shal
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/websockets/websocket_channel.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_context.h"
+#include "net/websockets/websocket_errors.h"
+#include "net/websockets/websocket_event_interface.h"
+#include "net/websockets/websocket_mux.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::MockFunction;
+
+// This mock is for testing expectations about how the EventInterface is used.
+class MockWebSocketEventInterface : public WebSocketEventInterface {
+ public:
+ MOCK_METHOD2(OnAddChannelResponse, void(bool, const std::string&));
+ MOCK_METHOD3(OnSendFrame,
+ void(bool, WebSocketMessageType, const std::vector<char>&));
+ MOCK_METHOD1(OnFlowControl, void(int64));
+ MOCK_METHOD2(OnDropChannel, void(unsigned short, const std::string&));
+};
+
+// This fake EventInterface is for tests which need an WebSocketEventInterface
+// implementation but are not verifying how it is used.
+class FakeWebSocketEventInterface : public WebSocketEventInterface {
+ virtual void OnAddChannelResponse(bool fail,
+ const std::string& selected_protocol) {}
+ virtual void OnSendFrame(bool fin,
+ WebSocketMessageType type,
+ const std::vector<char>& data) {}
+ virtual void OnFlowControl(int64 quota) {}
+ virtual void OnDropChannel(unsigned short reason,
+ const std::string& reason_text) {}
+};
+
+// This fake WebSocketStream is for tests that require a WebSocketStream but are
+// are not testing the way it is used. It has minimal functionality to return
+// the |protocol| and |extensions| that it was constructed with.
+class FakeWebSocketStream : public WebSocketStream {
+ public:
+ // Construct with empty protocol and extensions.
+ FakeWebSocketStream() : WebSocketStream(), protocol_(), extensions_() {}
+
+ // Construct with specified protocol and extensions.
+ FakeWebSocketStream(const std::string& protocol,
+ const std::string& extensions)
+ : WebSocketStream(), protocol_(protocol), extensions_(extensions) {}
+
+ virtual int SendHandshakeRequest(const GURL& url,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response_info,
+ const CompletionCallback& callback) {
+ return ERR_IO_PENDING;
+ }
+
+ virtual int ReadHandshakeResponse(const CompletionCallback& callback) {
+ return ERR_IO_PENDING;
+ }
+
+ virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) {
+ return ERR_IO_PENDING;
+ }
+
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) {
+ return ERR_IO_PENDING;
+ }
+
+ virtual void Close() {}
+
+ // Returns the string passed to the constructor.
+ virtual std::string Protocol() { return protocol_; }
+
+ // Returns the string passed to the constructor.
+ virtual std::string Extensions() { return extensions_; }
+
+ private:
+ // The string to return from Protocol().
+ std::string protocol_;
+
+ // The string to return from Extensions().
+ std::string extensions_;
+};
+
+// To make the static initialisers easier to read, we use enums rather than
+// bools.
+
+// NO_HEADER means there shouldn't be a header included in the generated
+// WebSocketFrameChunk. The static initialiser always has a header, but we can
+// avoid specifying the rest of the fields.
+enum IsFinal {
+ NO_HEADER,
+ NOT_FINAL_FRAME,
+ FINAL_FRAME
+};
+
+enum IsMasked {
+ NOT_MASKED,
+ MASKED
+};
+
+enum IsFinalChunk {
+ NOT_FINAL_CHUNK,
+ FINAL_CHUNK
+};
+
+// This is used to initialise a WebSocketFrameChunk but is statically
+// initialisable.
+struct InitFrameChunk {
+ struct FrameHeader {
+ IsFinal final;
+ // Reserved fields omitted for now. Add them if you need them.
+ WebSocketFrameHeader::OpCode opcode;
+ IsMasked masked;
+ uint64 payload_length;
+ };
+ FrameHeader header;
+
+ // Directly equivalent to WebSocketFrameChunk::final_chunk
+ IsFinalChunk final_chunk;
+
+ // Will be used to create the IOBuffer member. Can be NULL for null data.
+ const char *const data;
+};
+
+// Convert a const array of InitFrameChunks to the format used at
+// runtime. Templated on the size of the array to save typing.
+template <size_t N>
+ScopedVector<WebSocketFrameChunk> CreateFrameChunkVector(
+ const InitFrameChunk (&chunks)[N]) {
+ ScopedVector<WebSocketFrameChunk> vector;
+ vector.reserve(N);
+ for (size_t i = 0; i < N; ++i) {
+ scoped_ptr<WebSocketFrameChunk> chunk(new WebSocketFrameChunk);
+ if (chunks[i].header.final != NO_HEADER) {
+ const InitFrameChunk::FrameHeader& in_header = chunks[i].header;
+ scoped_ptr<WebSocketFrameHeader> out_header(
+ new WebSocketFrameHeader(in_header.opcode));
+ out_header->final = in_header.final == FINAL_FRAME;
+ out_header->opcode = in_header.opcode;
+ out_header->masked = in_header.masked == MASKED;
+ out_header->payload_length = in_header.payload_length;
+ chunk->header.swap(out_header);
+ }
+ chunk->final_chunk = chunks[i].final_chunk == FINAL_CHUNK;
+ chunk->data = new IOBufferWithSize(strlen(chunks[i].data));
+ memcpy(chunk->data->data(), chunks[i].data, strlen(chunks[i].data));
+ vector.push_back(chunk.release());
+ }
+ return vector.Pass();
+}
+
+// A FakeWebSocketStream whose ReadFrames() function returns data.
+class ReadableFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ enum IsSync {
+ SYNC,
+ ASYNC
+ };
+
+ // After constructing the object, call PrepareReadFrames() once for each
+ // time you wish it to return from the test.
+ ReadableFakeWebSocketStream()
+ : FakeWebSocketStream(), responses_(), index_(0) {}
+
+ // Call PrepareReadFrames() to prepare the fake responses, in order. If
+ // |async| is true, then ReadFrames() will return ERR_IO_PENDING and the
+ // callback will be scheduled to run on the message loop. This requires the
+ // test case to run the message loop. If |async| is false, the response will
+ // be returned synchronously. |error| is returned directly from ReadFrames()
+ // in the synchronous case, or passed to the callback in the asynchronous
+ // case. |chunks| will be converted to a ScopedVector<WebSocketFrameChunks>
+ // and copied to the pointer that was passed to ReadFrames().
+ template <size_t N>
+ void PrepareReadFrames(IsSync async,
+ int error,
+ const InitFrameChunk (&chunks)[N]) {
+ responses_.push_back(
+ new Response(async, error, CreateFrameChunkVector(chunks)));
+ }
+
+ // Prepare a fake error response (ie. there is no data).
+ void PrepareReadFramesError(IsSync async, int error) {
+ responses_.push_back(
+ new Response(async, error, ScopedVector<WebSocketFrameChunk>()));
+ }
+
+ virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) {
+ if (index_ >= responses_.size()) {
+ return ERR_IO_PENDING;
+ }
+ if (responses_[index_]->async == ASYNC) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ReadableFakeWebSocketStream::DoCallback,
+ base::Unretained(this),
+ frame_chunks,
+ callback));
+ return ERR_IO_PENDING;
+ } else {
+ frame_chunks->swap(responses_[index_]->chunks);
+ return responses_[index_++]->error;
+ }
+ }
+
+ private:
+ void DoCallback(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) {
+ frame_chunks->swap(responses_[index_]->chunks);
+ callback.Run(responses_[index_++]->error);
+ return;
+ }
+
+ struct Response {
+ Response(IsSync async, int error, ScopedVector<WebSocketFrameChunk> chunks)
+ : async(async), error(error), chunks(chunks.Pass()) {}
+
+ IsSync async;
+ int error;
+ ScopedVector<WebSocketFrameChunk> chunks;
+
+ private:
+ // Bad things will happen if we attempt to copy or assign "chunks".
+ DISALLOW_COPY_AND_ASSIGN(Response);
+ };
+ ScopedVector<Response> responses_;
+ size_t index_;
+};
+
+// A FakeWebSocketStream where writes always complete successfully and
+// synchronously.
+class WriteableFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) {
+ return OK;
+ }
+};
+
+struct ArgumentCopyingWebSocketFactory {
+ void Factory(const GURL& socket_url,
+ const std::vector<std::string>& requested_protocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context,
+ const WebSocketStream::SuccessCallback& on_success,
+ const WebSocketStream::FailureCallback& on_failure) {
+ this->socket_url = socket_url;
+ this->requested_protocols = requested_protocols;
+ this->origin = origin;
+ this->url_request_context = url_request_context;
+ this->on_success = on_success;
+ this->on_failure = on_failure;
+ }
+
+ GURL socket_url;
+ GURL origin;
+ std::vector<std::string> requested_protocols;
+ URLRequestContext* url_request_context;
+ WebSocketStream::SuccessCallback on_success;
+ WebSocketStream::FailureCallback on_failure;
+};
+
+// The WebSocketEventInterface handles data as vector<char>. For testing
+// purposes, this function allows a vector<char> to be easily created from a
+// string.
+std::vector<char> AsVector(const std::string& s) {
+ return std::vector<char>(s.begin(), s.end());
+}
+
+// Base class for WebSocketChannelTests.
+class WebSocketChannelTest : public ::testing::Test {
+ protected:
+ WebSocketChannelTest()
+ : data_(), channel_(), stream_(new FakeWebSocketStream) {}
+
+ // Create a new WebSocketChannel and connect it, using the settings stored in
+ // data_.
+ void CreateChannelAndConnect() {
+ channel_.reset(
+ new WebSocketChannel(data_.url, EventInterface()));
+ channel_->SendAddChannelRequestWithFactory(
+ data_.requested_protocols,
+ data_.origin,
+ &data_.url_request_context,
+ base::Bind(&ArgumentCopyingWebSocketFactory::Factory,
+ base::Unretained(&data_.factory)));
+ }
+
+ // Same as CreateChannelAndConnect(), but call the on_success callback as
+ // well.
+ void CreateChannelAndConnectSuccessfully() {
+ CreateChannelAndConnect();
+ data_.factory.on_success.Run(stream_.Pass());
+ }
+
+ // Return a WebSocketEventInterface to be passed to the WebSocketChannel.
+ // This implementation returns a newly-created fake. Subclasses may return a
+ // mock instead.
+ virtual scoped_ptr<WebSocketEventInterface> EventInterface() {
+ return scoped_ptr<WebSocketEventInterface>(new FakeWebSocketEventInterface);
+ }
+
+ // Downcasting and moving a scoped_ptr in one operation doesn't seem to work
+ // (bug?). This function works around that and simplifies initialising stream_
+ // from a subclass.
+ template<class T>
+ void set_stream(scoped_ptr<T> stream) {
+ stream_.reset(stream.release());
+ }
+
+ // A struct containing the data that will be used to connect the channel.
+ struct ConnectData {
+ // URL to (pretend to) connect to.
+ GURL url;
+ // Origin of the request
+ GURL origin;
+ // Requested protocols for the request.
+ std::vector<std::string> requested_protocols;
+ // URLRequestContext object.
+ URLRequestContext url_request_context;
+ // A fake WebSocketFactory that just records its arguments.
+ ArgumentCopyingWebSocketFactory factory;
+ };
+ ConnectData data_;
+
+ // The channel we are testing. Not initialised until SetChannel() is called.
+ scoped_ptr<WebSocketChannel> channel_;
+
+ // A mock or fake stream for tests that need one.
+ scoped_ptr<WebSocketStream> stream_;
+};
+
+// Base class for tests which verify that EventInterface methods are called
+// appropriately.
+class WebSocketChannelEventInterfaceTest : public WebSocketChannelTest {
+ protected:
+ WebSocketChannelEventInterfaceTest()
+ : WebSocketChannelTest(),
+ event_interface_(new MockWebSocketEventInterface) {}
+
+ virtual scoped_ptr<WebSocketEventInterface> EventInterface() OVERRIDE {
+ return scoped_ptr<WebSocketEventInterface>(event_interface_.release());
+ }
+
+ scoped_ptr<MockWebSocketEventInterface> event_interface_;
+};
+
+// Simple test that everything that should be passed to the factory function is
+// passed to the factory function.
+TEST_F(WebSocketChannelTest, EverythingIsPassedToTheFactoryFunction) {
+ data_.url = GURL("ws://example.com/test");
+ data_.origin = GURL("http://example.com/test");
+ data_.requested_protocols.push_back("Sinbad");
+
+ CreateChannelAndConnect();
+
+ EXPECT_EQ(data_.url, data_.factory.socket_url);
+ EXPECT_EQ(data_.origin, data_.factory.origin);
+ EXPECT_EQ(data_.requested_protocols, data_.factory.requested_protocols);
+ EXPECT_EQ(&data_.url_request_context, data_.factory.url_request_context);
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectSuccessReported) {
+ // false means success.
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, ""));
+ // OnFlowControl is always called immediately after connect to provide initial
+ // quota to the renderer.
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+
+ CreateChannelAndConnect();
+
+ data_.factory.on_success.Run(stream_.Pass());
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectFailureReported) {
+ // true means failure.
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(true, ""));
+
+ CreateChannelAndConnect();
+
+ data_.factory.on_failure.Run(kWebSocketErrorNoStatusReceived);
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, ProtocolPassed) {
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, "Bob"));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+
+ CreateChannelAndConnect();
+
+ data_.factory.on_success
+ .Run(scoped_ptr<WebSocketStream>(new FakeWebSocketStream("Bob", "")));
+}
+
+// The first frames from the server can arrive together with the handshake, in
+// which case they will be available as soon as ReadFrames() is called the first
+// time.
+TEST_F(WebSocketChannelEventInterfaceTest, DataLeftFromHandshake) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "HELLO"},
+ };
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnSendFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// A remote server could accept the handshake, but then immediately send a
+// close frame.
+TEST_F(WebSocketChannelEventInterfaceTest, CloseAfterHandshake) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 23},
+ FINAL_CHUNK, "\x03\xf3Internal Server Error"},
+ };
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorInternalServerError,
+ "Internal Server Error"));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// A remote server could close the connection immediately after sending the
+// handshake response (most likely a bug in the server).
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectionCloseAfterHandshake) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure,
+ "Abnormal Closure"));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, NormalAsyncRead) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "HELLO"},
+ };
+ // We use this checkpoint object to verify that the callback isn't called
+ // until we expect it to be.
+ MockFunction<void(int)> checkpoint;
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(
+ *event_interface_,
+ OnSendFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO")));
+ EXPECT_CALL(checkpoint, Call(2));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ base::MessageLoop::current()->RunUntilIdle();
+ checkpoint.Call(2);
+}
+
+// Extra data can arrive while a read is being processed, resulting in the next
+// read completing synchronously.
+TEST_F(WebSocketChannelEventInterfaceTest, AsyncThenSyncRead) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks1[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "HELLO"},
+ };
+ static const InitFrameChunk chunks2[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "WORLD"},
+ };
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks2);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnSendFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnSendFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("WORLD")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Data frames that arrive in fragments are turned into individual frames
+TEST_F(WebSocketChannelEventInterfaceTest, FragmentedFrames) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ // Here we have one message split into 3 frames which arrive in 3 chunks. The
+ // first frame is entirely in the first chunk, the second frame is split
+ // across all the chunks, and the final frame is entirely in the final
+ // chunk. This should be delivered as 5 frames.
+ static const InitFrameChunk chunks1[] = {
+ {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "THREE"},
+ {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED,
+ 7},
+ NOT_FINAL_CHUNK, " "},
+ };
+ static const InitFrameChunk chunks2[] = {{{NO_HEADER}, NOT_FINAL_CHUNK,
+ "SMALL"}};
+ static const InitFrameChunk chunks3[] = {
+ {{NO_HEADER}, FINAL_CHUNK, " "},
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 6},
+ FINAL_CHUNK, "FRAMES"},
+ };
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnSendFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("THREE")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnSendFrame(
+ false, WebSocketFrameHeader::kOpCodeContinuation, AsVector(" ")));
+ EXPECT_CALL(*event_interface_,
+ OnSendFrame(false,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector("SMALL")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnSendFrame(
+ false, WebSocketFrameHeader::kOpCodeContinuation, AsVector(" ")));
+ EXPECT_CALL(*event_interface_,
+ OnSendFrame(true,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector("FRAMES")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// In the case when a single-frame message because fragmented, it must be
+// correctly transformed to multiple frames.
+TEST_F(WebSocketChannelEventInterfaceTest, MessageFragmentation) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ // Here we have one message split into 3 frames which arrive in 3 chunks. The
+ // first frame is entirely in the first chunk, the second frame is split
+ // across all the chunks, and the final frame is entirely in the final
+ // chunk. This should be delivered as 5 frames.
+ static const InitFrameChunk chunks1[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 12},
+ NOT_FINAL_CHUNK, "TIME"},
+ };
+ static const InitFrameChunk chunks2[] = {{{NO_HEADER}, NOT_FINAL_CHUNK,
+ " FOR "}};
+ static const InitFrameChunk chunks3[] = {{{NO_HEADER}, FINAL_CHUNK, "TEA"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnSendFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("TIME")));
+ EXPECT_CALL(*event_interface_,
+ OnSendFrame(false,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector(" FOR ")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnSendFrame(
+ true, WebSocketFrameHeader::kOpCodeContinuation, AsVector("TEA")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// If a control message is fragmented, it must be re-assembled before being
+// delivered. A control message can only be fragmented at the network level; it
+// is not permitted to be split into multiple frames.
+TEST_F(WebSocketChannelEventInterfaceTest, FragmentedControlMessage) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks1[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7},
+ NOT_FINAL_CHUNK, "\x03\xe8"},
+ };
+ static const InitFrameChunk chunks2[] = {{{NO_HEADER}, NOT_FINAL_CHUNK,
+ "Clo"}};
+ static const InitFrameChunk chunks3[] = {{{NO_HEADER}, FINAL_CHUNK,
+ "se"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketNormalClosure, "Close"));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Connection closed unexpectedly.
+TEST_F(WebSocketChannelEventInterfaceTest, AsyncAbnormalClosure) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Connection reset.
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectionReset) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
+ ERR_CONNECTION_RESET);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Connection closed in the middle of a Close message (server bug, etc.)
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectionClosedInMessage) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7},
+ NOT_FINAL_CHUNK, "\x03\xe8"},
+ };
+
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+
+
+
+// If the renderer sends lots of small writes, we don't want to update the quota
+// for each one.
+TEST_F(WebSocketChannelEventInterfaceTest, SmallWriteDoesntUpdateQuota) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(true, WebSocketFrameHeader::kOpCodeText, AsVector("B"));
+}
+
+// If we send enough to go below send_quota_low_water_mask_ we should get our
+// quota refreshed.
+TEST_F(WebSocketChannelEventInterfaceTest, LargeWriteUpdatesQuota) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ // We use this checkpoint object to verify that the quota update comes after
+ // the write.
+ MockFunction<void(int)> checkpoint;
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(2));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ // TODO(ricea): If kDefaultSendQuotaHighWaterMark changes, then this value
+ // will need to be updated.
+ channel_->SendFrame(
+ true, WebSocketFrameHeader::kOpCodeText, std::vector<char>(1 << 17, 'B'));
+ checkpoint.Call(2);
+}
+
+// Verify that our quota actually is refreshed when we are told it is.
+TEST_F(WebSocketChannelEventInterfaceTest, QuotaReallyIsRefreshed) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ MockFunction<void(int)> checkpoint;
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(2));
+ // If quota was not really refreshed, we would get an OnDropChannel()
+ // message.
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(3));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ // TODO(ricea): If kDefaultSendQuotaLowWaterMark and/or
+ // kDefaultSendQuotaHighWaterMark change, then this value will need to be
+ // updated.
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>((1 << 16) + 1, 'D'));
+ checkpoint.Call(2);
+ // We should have received more quota at this point.
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>((1 << 16) + 1, 'E'));
+ checkpoint.Call(3);
+}
+
+// If we send more than the available quota then the connection will be closed
+// with an error.
+TEST_F(WebSocketChannelEventInterfaceTest, WriteOverQuotaIsRejected) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ // TODO(ricea): Change this if kDefaultSendQuotaHighWaterMark changes.
+ EXPECT_CALL(*event_interface_, OnFlowControl(1 << 17));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketMuxErrorSendQuotaViolation, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>((1 << 17) + 1, 'C'));
+}
+
+} // namespace
+} // namespace net

Powered by Google App Engine
This is Rietveld 408576698