Index: net/spdy/spdy_websocket_stream_unittest.cc |
diff --git a/net/spdy/spdy_websocket_stream_unittest.cc b/net/spdy/spdy_websocket_stream_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..05f3110b60a0bc723aabee1ced47bcb3a263b60d |
--- /dev/null |
+++ b/net/spdy/spdy_websocket_stream_unittest.cc |
@@ -0,0 +1,433 @@ |
+// Copyright (c) 2010 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 <string> |
+#include <vector> |
+ |
+#include "net/spdy/spdy_websocket_stream.h" |
+#include "net/base/completion_callback.h" |
+#include "net/proxy/proxy_server.h" |
+#include "net/spdy/spdy_protocol.h" |
+#include "net/spdy/spdy_session.h" |
+#include "net/spdy/spdy_test_util.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace net { |
+ |
+spdy::SpdyFrame* ConstructSpdyWebSocketHandshakeReq( |
+ const char* const url, |
+ const char* const origin, |
+ const char* const protocol, |
+ bool compressed, |
+ spdy::SpdyStreamId stream_id, |
+ RequestPriority request_priority) { |
+ const SpdyHeaderInfo kSynStreamHeader = { |
+ spdy::SYN_STREAM, |
+ stream_id, |
+ 0, // Associated stream ID |
+ net::ConvertRequestPriorityToSpdyPriority(request_priority), |
+ spdy::CONTROL_FLAG_NONE, |
+ compressed, |
+ spdy::INVALID, // Status |
+ NULL, // Data, |
+ 0, // Length |
+ spdy::DATA_FLAG_NONE |
+ }; |
+ |
+ const char* const headers[] = { |
+ "url", |
+ url, |
+ "origin", |
+ origin, |
+ "protocol", |
+ protocol, |
+ }; |
+ int header_size = arraysize(headers) / 2; |
+ if (protocol == NULL) |
+ header_size -= 1; |
+ |
+ return ConstructSpdyPacket( |
+ kSynStreamHeader, |
+ NULL, |
+ 0, |
+ headers, |
+ header_size); |
+} |
+ |
+spdy::SpdyFrame* ConstructSpdyWebSocketHandshakeResp( |
+ const char* const url, |
+ const char* const origin, |
+ const char* const protocol, |
+ bool compressed, |
+ spdy::SpdyStreamId stream_id, |
+ RequestPriority request_priority) { |
+ const SpdyHeaderInfo kSynReplyHeader = { |
+ spdy::SYN_REPLY, |
+ stream_id, |
+ 0, // Associated stream ID |
+ net::ConvertRequestPriorityToSpdyPriority(request_priority), |
+ spdy::CONTROL_FLAG_NONE, |
+ false, |
+ spdy::INVALID, // Status |
+ NULL, // Data |
+ 0, // Length |
+ spdy::DATA_FLAG_NONE |
+ }; |
+ |
+ const char* const headers[] = { |
+ "sec-websocket-location", |
+ url, |
+ "sec-websocket-origin", |
+ origin, |
+ "sec-websocket-protocol", |
+ protocol, |
+ }; |
+ int header_size = arraysize(headers) / 2; |
+ if (protocol == NULL) |
+ header_size -= 1; |
+ |
+ return ConstructSpdyPacket( |
+ kSynReplyHeader, |
+ NULL, |
+ 0, |
+ headers, |
+ header_size); |
+} |
+ |
+spdy::SpdyFrame* ConstructSpdyWebSocketFrame( |
+ const char* data, |
+ int len, |
+ spdy::SpdyStreamId stream_id, |
+ bool fin) { |
+ spdy::SpdyFramer framer; |
+ return framer.CreateDataFrame( |
+ stream_id, data, len, |
+ fin ? spdy::DATA_FLAG_FIN : spdy::DATA_FLAG_NONE); |
+} |
+ |
+struct SpdyWebSocketStreamEvent { |
+ enum EventType { |
+ EVENT_CREATED, |
+ EVENT_SENT_HEADERS, EVENT_RECEIVED_HEADER, |
+ EVENT_SENT_DATA, EVENT_RECEIVED_DATA, |
+ EVENT_CLOSE, |
+ }; |
+ SpdyWebSocketStreamEvent(EventType type, |
+ SpdyWebSocketStream* stream, |
+ const spdy::SpdyHeaderBlock& headers, |
+ int result, |
+ const std::string& data) |
+ : event_type(type), |
+ stream(stream), |
+ headers(headers), |
+ result(result), |
+ data(data) {} |
+ |
+ EventType event_type; |
+ SpdyWebSocketStream* stream; |
+ spdy::SpdyHeaderBlock headers; |
+ int result; |
+ std::string data; |
+}; |
+ |
+class SpdyWebSocketStreamEventRecorder : public SpdyWebSocketStreamDelegate { |
+ public: |
+ explicit SpdyWebSocketStreamEventRecorder(CompletionCallback* callback) |
+ : on_created_(NULL), |
+ on_sent_headers_(NULL), |
+ on_received_header_(NULL), |
+ on_sent_data_(NULL), |
+ on_received_data_(NULL), |
+ on_close_(NULL), |
+ callback_(callback) {} |
+ virtual ~SpdyWebSocketStreamEventRecorder() { |
+ delete on_created_; |
+ delete on_sent_headers_; |
+ delete on_received_header_; |
+ delete on_sent_data_; |
+ delete on_received_data_; |
+ delete on_close_; |
+ } |
+ |
+ void SetOnCreated(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) { |
+ on_created_ = callback; |
+ } |
+ void SetOnSentHeaders(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) { |
+ on_sent_headers_ = callback; |
+ } |
+ void SetOnReceivedHeader( |
+ Callback1<SpdyWebSocketStreamEvent*>::Type* callback) { |
+ on_received_header_ = callback; |
+ } |
+ void SetOnSentData(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) { |
+ on_sent_data_ = callback; |
+ } |
+ void SetOnReceivedData(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) { |
+ on_received_data_ = callback; |
+ } |
+ void SetOnClose(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) { |
+ on_close_ = callback; |
+ } |
+ |
+ virtual void OnCreatedSpdyStream(SpdyWebSocketStream* stream, int result) { |
+ events_.push_back( |
+ SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_CREATED, |
+ stream, |
+ spdy::SpdyHeaderBlock(), |
+ result, |
+ std::string())); |
+ if (on_created_) |
+ on_created_->Run(&events_.back()); |
+ } |
+ |
+ virtual void OnSentSpdyHeaders(SpdyWebSocketStream* stream) { |
+ events_.push_back( |
+ SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, |
+ stream, |
+ spdy::SpdyHeaderBlock(), |
+ OK, |
+ std::string())); |
+ if (on_sent_data_) |
+ on_sent_data_->Run(&events_.back()); |
+ } |
+ virtual int OnReceivedSpdyResponseHeader( |
+ SpdyWebSocketStream* stream, |
+ const spdy::SpdyHeaderBlock& headers, |
+ int status) { |
+ events_.push_back( |
+ SpdyWebSocketStreamEvent( |
+ SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, |
+ stream, |
+ headers, |
+ status, |
+ std::string())); |
+ if (on_received_header_) |
+ on_received_header_->Run(&events_.back()); |
+ return status; |
+ } |
+ virtual void OnSentSpdyData( |
+ SpdyWebSocketStream* stream, int amount_sent) { |
+ events_.push_back( |
+ SpdyWebSocketStreamEvent( |
+ SpdyWebSocketStreamEvent::EVENT_SENT_DATA, |
+ stream, |
+ spdy::SpdyHeaderBlock(), |
+ amount_sent, |
+ std::string())); |
+ if (on_sent_data_) |
+ on_sent_data_->Run(&events_.back()); |
+ } |
+ virtual void OnReceivedSpdyData( |
+ SpdyWebSocketStream* stream, const char* data, int length) { |
+ events_.push_back( |
+ SpdyWebSocketStreamEvent( |
+ SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, |
+ stream, |
+ spdy::SpdyHeaderBlock(), |
+ length, |
+ std::string(data, length))); |
+ if (on_received_data_) |
+ on_received_data_->Run(&events_.back()); |
+ } |
+ virtual void OnCloseSpdyStream(SpdyWebSocketStream* stream) { |
+ events_.push_back( |
+ SpdyWebSocketStreamEvent( |
+ SpdyWebSocketStreamEvent::EVENT_CLOSE, |
+ stream, |
+ spdy::SpdyHeaderBlock(), |
+ OK, |
+ std::string())); |
+ if (on_close_) |
+ on_close_->Run(&events_.back()); |
+ if (callback_) |
+ callback_->Run(net::OK); |
+ } |
+ |
+ const std::vector<SpdyWebSocketStreamEvent>& GetSeenEvents() const { |
+ return events_; |
+ } |
+ |
+ private: |
+ std::vector<SpdyWebSocketStreamEvent> events_; |
+ Callback1<SpdyWebSocketStreamEvent*>::Type* on_created_; |
+ Callback1<SpdyWebSocketStreamEvent*>::Type* on_sent_headers_; |
+ Callback1<SpdyWebSocketStreamEvent*>::Type* on_received_header_; |
+ Callback1<SpdyWebSocketStreamEvent*>::Type* on_sent_data_; |
+ Callback1<SpdyWebSocketStreamEvent*>::Type* on_received_data_; |
+ Callback1<SpdyWebSocketStreamEvent*>::Type* on_close_; |
+ CompletionCallback* callback_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(SpdyWebSocketStreamEventRecorder); |
+}; |
+ |
+class SpdyWebSocketStreamTest : public testing::Test { |
+ public: |
+ OrderedSocketData* data() { return data_; } |
+ |
+ void DoSendHelloFrame(SpdyWebSocketStreamEvent* event) { |
+ std::string frame; |
+ frame.append(1, '\0'); |
+ frame.append("hello"); |
+ frame.append(1, '\xff'); |
+ event->stream->SendData(frame.data(), frame.size()); |
+ } |
+ |
+ void DoSendClosingFrame(SpdyWebSocketStreamEvent* event) { |
+ static const char kClosingFrame[] = "\xff\0"; |
+ event->stream->SendData(kClosingFrame, 2); |
+ } |
+ |
+ void DoClose(SpdyWebSocketStreamEvent* event) { |
+ event->stream->Close(); |
+ } |
+ |
+ protected: |
+ SpdyWebSocketStreamTest() {} |
+ virtual ~SpdyWebSocketStreamTest() {} |
+ |
+ virtual void SetUp() {} |
+ virtual void TearDown() { |
+ MessageLoop::current()->RunAllPending(); |
+ } |
+ |
+ void EnableCompression(bool enabled) { |
+ spdy::SpdyFramer::set_enable_compression_default(enabled); |
+ } |
+ int InitSession(MockRead* reads, size_t reads_count, |
+ MockWrite* writes, size_t writes_count, |
+ HostPortPair& host_port_pair) { |
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); |
+ data_ = new OrderedSocketData(reads, reads_count, writes, writes_count); |
+ session_deps_.socket_factory->AddSocketDataProvider(data_.get()); |
+ http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); |
+ session_ = http_session_->spdy_session_pool()-> |
+ Get(pair, http_session_->mutable_spdy_settings(), BoundNetLog()); |
+ tcp_params_ = new TCPSocketParams(host_port_pair.host(), |
+ host_port_pair.port(), |
+ MEDIUM, GURL(), false); |
+ TestCompletionCallback callback; |
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); |
+ EXPECT_EQ(ERR_IO_PENDING, |
+ connection->Init(host_port_pair.ToString(), tcp_params_, MEDIUM, |
+ &callback, http_session_->tcp_socket_pool(), |
+ BoundNetLog())); |
+ EXPECT_EQ(OK, callback.WaitForResult()); |
+ return session_->InitializeWithSocket(connection.release(), false, OK); |
+ } |
+ |
+ SpdySessionDependencies session_deps_; |
+ scoped_refptr<OrderedSocketData> data_; |
+ scoped_refptr<HttpNetworkSession> http_session_; |
+ scoped_refptr<SpdySession> session_; |
+ scoped_refptr<TCPSocketParams> tcp_params_; |
+}; |
+ |
+TEST_F(SpdyWebSocketStreamTest, Echo) { |
+ EnableCompression(false); |
+ SpdySession::SetSSLMode(false); |
+ |
+ spdy::SpdyStreamId stream_id = 1; |
+ |
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyWebSocketHandshakeReq( |
+ "ws://example.com/echo", |
+ "http://example.com/wsdemo", |
+ NULL, |
+ false, |
+ stream_id, |
+ HIGHEST)); |
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyWebSocketHandshakeResp( |
+ "ws://example.com/echo", |
+ "http://example.com/wsdemo", |
+ NULL, |
+ false, |
+ stream_id, |
+ HIGHEST)); |
+ |
+ static const char* const msg_frame = "\0hello\xff"; |
+ scoped_ptr<spdy::SpdyFrame> msg(ConstructSpdyWebSocketFrame( |
+ msg_frame, 7, stream_id, false)); |
+ |
+ static const char* const closing_frame = "\xff\0"; |
+ scoped_ptr<spdy::SpdyFrame> closing(ConstructSpdyWebSocketFrame( |
+ closing_frame, 2, stream_id, false)); |
+ |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req.get(), 1), |
+ CreateMockWrite(*msg.get(), 3), |
+ CreateMockWrite(*closing.get(), 5) |
+ }; |
+ |
+ MockRead reads[] = { |
+ CreateMockRead(*resp.get(), 2), |
+ CreateMockRead(*msg.get(), 4), |
+ // Skip sequence 6 to notify closing has been sent. |
+ CreateMockRead(*closing.get(), 7), |
+ MockRead(false, 0, 8) // EOF |
+ }; |
+ |
+ HostPortPair host_port_pair("example.com", 80); |
+ HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); |
+ EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes), |
+ host_port_pair)); |
+ |
+ TestCompletionCallback callback; |
+ |
+ scoped_ptr<SpdyWebSocketStreamEventRecorder> delegate( |
+ new SpdyWebSocketStreamEventRecorder(&callback)); |
+ // Necessary for NewCallback. |
+ SpdyWebSocketStreamTest* test = this; |
+ delegate->SetOnReceivedHeader( |
+ NewCallback(test, &SpdyWebSocketStreamTest::DoSendHelloFrame)); |
+ delegate->SetOnReceivedData( |
+ NewCallback(test, &SpdyWebSocketStreamTest::DoSendClosingFrame)); |
+ |
+ scoped_ptr<SpdyWebSocketStream> websocket_stream( |
+ new SpdyWebSocketStream(session_, delegate.get())); |
+ |
+ BoundNetLog net_log; |
+ GURL url("ws://example.com/echo"); |
+ ASSERT_EQ(OK, websocket_stream->InitializeStream(url, HIGHEST, net_log)); |
+ |
+ EXPECT_EQ(stream_id, websocket_stream->stream_id()); |
+ |
+ |
+ linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock); |
+ (*headers)["url"] = "ws://example.com/echo"; |
+ (*headers)["origin"] = "http://example.com/wsdemo"; |
+ |
+ websocket_stream->SendRequest(headers); |
+ |
+ callback.WaitForResult(); |
+ |
+ const std::vector<SpdyWebSocketStreamEvent>& events = |
+ delegate->GetSeenEvents(); |
+ ASSERT_EQ(8U, events.size()); |
+ |
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CREATED, |
+ events[0].event_type); |
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, |
+ events[1].event_type); |
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, |
+ events[2].event_type); |
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, |
+ events[3].event_type); |
+ EXPECT_EQ(7, events[3].result); |
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, |
+ events[4].event_type); |
+ EXPECT_EQ(7, events[4].result); |
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, |
+ events[5].event_type); |
+ EXPECT_EQ(2, events[5].result); |
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, |
+ events[6].event_type); |
+ EXPECT_EQ(2, events[6].result); |
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE, |
+ events[7].event_type); |
+ |
+ EXPECT_TRUE(!http_session_->spdy_session_pool()->HasSession(pair)); |
+ EXPECT_TRUE(data()->at_read_eof()); |
+ EXPECT_TRUE(data()->at_write_eof()); |
+} |
+ |
+} // namespace net |