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

Side by Side Diff: net/websockets/websocket_job_unittest.cc

Issue 23614009: Rename websockets/*_unittest.cc to *_test.cc (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 7 years, 3 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 unified diff | Download patch
« no previous file with comments | « net/websockets/websocket_job_test.cc ('k') | net/websockets/websocket_net_log_params_test.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/websockets/websocket_job.h"
6
7 #include <string>
8 #include <vector>
9
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/callback.h"
13 #include "base/memory/ref_counted.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "net/base/completion_callback.h"
17 #include "net/base/net_errors.h"
18 #include "net/base/test_completion_callback.h"
19 #include "net/cookies/cookie_store.h"
20 #include "net/cookies/cookie_store_test_helpers.h"
21 #include "net/dns/mock_host_resolver.h"
22 #include "net/http/http_transaction_factory.h"
23 #include "net/http/transport_security_state.h"
24 #include "net/proxy/proxy_service.h"
25 #include "net/socket/next_proto.h"
26 #include "net/socket/socket_test_util.h"
27 #include "net/socket_stream/socket_stream.h"
28 #include "net/spdy/spdy_session.h"
29 #include "net/spdy/spdy_websocket_test_util.h"
30 #include "net/ssl/ssl_config_service.h"
31 #include "net/url_request/url_request_context.h"
32 #include "net/websockets/websocket_throttle.h"
33 #include "testing/gmock/include/gmock/gmock.h"
34 #include "testing/gtest/include/gtest/gtest.h"
35 #include "testing/platform_test.h"
36 #include "url/gurl.h"
37
38 namespace net {
39
40 namespace {
41
42 class MockSocketStream : public SocketStream {
43 public:
44 MockSocketStream(const GURL& url, SocketStream::Delegate* delegate)
45 : SocketStream(url, delegate) {}
46
47 virtual void Connect() OVERRIDE {}
48 virtual bool SendData(const char* data, int len) OVERRIDE {
49 sent_data_ += std::string(data, len);
50 return true;
51 }
52
53 virtual void Close() OVERRIDE {}
54 virtual void RestartWithAuth(
55 const AuthCredentials& credentials) OVERRIDE {
56 }
57
58 virtual void DetachDelegate() OVERRIDE {
59 delegate_ = NULL;
60 }
61
62 const std::string& sent_data() const {
63 return sent_data_;
64 }
65
66 protected:
67 virtual ~MockSocketStream() {}
68
69 private:
70 std::string sent_data_;
71 };
72
73 class MockSocketStreamDelegate : public SocketStream::Delegate {
74 public:
75 MockSocketStreamDelegate()
76 : amount_sent_(0), allow_all_cookies_(true) {}
77 void set_allow_all_cookies(bool allow_all_cookies) {
78 allow_all_cookies_ = allow_all_cookies;
79 }
80 virtual ~MockSocketStreamDelegate() {}
81
82 void SetOnStartOpenConnection(const base::Closure& callback) {
83 on_start_open_connection_ = callback;
84 }
85 void SetOnConnected(const base::Closure& callback) {
86 on_connected_ = callback;
87 }
88 void SetOnSentData(const base::Closure& callback) {
89 on_sent_data_ = callback;
90 }
91 void SetOnReceivedData(const base::Closure& callback) {
92 on_received_data_ = callback;
93 }
94 void SetOnClose(const base::Closure& callback) {
95 on_close_ = callback;
96 }
97
98 virtual int OnStartOpenConnection(
99 SocketStream* socket,
100 const CompletionCallback& callback) OVERRIDE {
101 if (!on_start_open_connection_.is_null())
102 on_start_open_connection_.Run();
103 return OK;
104 }
105 virtual void OnConnected(SocketStream* socket,
106 int max_pending_send_allowed) OVERRIDE {
107 if (!on_connected_.is_null())
108 on_connected_.Run();
109 }
110 virtual void OnSentData(SocketStream* socket,
111 int amount_sent) OVERRIDE {
112 amount_sent_ += amount_sent;
113 if (!on_sent_data_.is_null())
114 on_sent_data_.Run();
115 }
116 virtual void OnReceivedData(SocketStream* socket,
117 const char* data, int len) OVERRIDE {
118 received_data_ += std::string(data, len);
119 if (!on_received_data_.is_null())
120 on_received_data_.Run();
121 }
122 virtual void OnClose(SocketStream* socket) OVERRIDE {
123 if (!on_close_.is_null())
124 on_close_.Run();
125 }
126 virtual bool CanGetCookies(SocketStream* socket,
127 const GURL& url) OVERRIDE {
128 return allow_all_cookies_;
129 }
130 virtual bool CanSetCookie(SocketStream* request,
131 const GURL& url,
132 const std::string& cookie_line,
133 CookieOptions* options) OVERRIDE {
134 return allow_all_cookies_;
135 }
136
137 size_t amount_sent() const { return amount_sent_; }
138 const std::string& received_data() const { return received_data_; }
139
140 private:
141 int amount_sent_;
142 bool allow_all_cookies_;
143 std::string received_data_;
144 base::Closure on_start_open_connection_;
145 base::Closure on_connected_;
146 base::Closure on_sent_data_;
147 base::Closure on_received_data_;
148 base::Closure on_close_;
149 };
150
151 class MockCookieStore : public CookieStore {
152 public:
153 struct Entry {
154 GURL url;
155 std::string cookie_line;
156 CookieOptions options;
157 };
158
159 MockCookieStore() {}
160
161 bool SetCookieWithOptions(const GURL& url,
162 const std::string& cookie_line,
163 const CookieOptions& options) {
164 Entry entry;
165 entry.url = url;
166 entry.cookie_line = cookie_line;
167 entry.options = options;
168 entries_.push_back(entry);
169 return true;
170 }
171
172 std::string GetCookiesWithOptions(const GURL& url,
173 const CookieOptions& options) {
174 std::string result;
175 for (size_t i = 0; i < entries_.size(); i++) {
176 Entry& entry = entries_[i];
177 if (url == entry.url) {
178 if (!result.empty()) {
179 result += "; ";
180 }
181 result += entry.cookie_line;
182 }
183 }
184 return result;
185 }
186
187 // CookieStore:
188 virtual void SetCookieWithOptionsAsync(
189 const GURL& url,
190 const std::string& cookie_line,
191 const CookieOptions& options,
192 const SetCookiesCallback& callback) OVERRIDE {
193 bool result = SetCookieWithOptions(url, cookie_line, options);
194 if (!callback.is_null())
195 callback.Run(result);
196 }
197
198 virtual void GetCookiesWithOptionsAsync(
199 const GURL& url,
200 const CookieOptions& options,
201 const GetCookiesCallback& callback) OVERRIDE {
202 if (!callback.is_null())
203 callback.Run(GetCookiesWithOptions(url, options));
204 }
205
206 virtual void DeleteCookieAsync(const GURL& url,
207 const std::string& cookie_name,
208 const base::Closure& callback) OVERRIDE {
209 ADD_FAILURE();
210 }
211
212 virtual void DeleteAllCreatedBetweenAsync(
213 const base::Time& delete_begin,
214 const base::Time& delete_end,
215 const DeleteCallback& callback) OVERRIDE {
216 ADD_FAILURE();
217 }
218
219 virtual void DeleteSessionCookiesAsync(const DeleteCallback&) OVERRIDE {
220 ADD_FAILURE();
221 }
222
223 virtual CookieMonster* GetCookieMonster() OVERRIDE { return NULL; }
224
225 const std::vector<Entry>& entries() const { return entries_; }
226
227 private:
228 friend class base::RefCountedThreadSafe<MockCookieStore>;
229 virtual ~MockCookieStore() {}
230
231 std::vector<Entry> entries_;
232 };
233
234 class MockSSLConfigService : public SSLConfigService {
235 public:
236 virtual void GetSSLConfig(SSLConfig* config) OVERRIDE {}
237
238 protected:
239 virtual ~MockSSLConfigService() {}
240 };
241
242 class MockURLRequestContext : public URLRequestContext {
243 public:
244 explicit MockURLRequestContext(CookieStore* cookie_store)
245 : transport_security_state_() {
246 set_cookie_store(cookie_store);
247 set_transport_security_state(&transport_security_state_);
248 base::Time expiry = base::Time::Now() + base::TimeDelta::FromDays(1000);
249 bool include_subdomains = false;
250 transport_security_state_.AddHSTS("upgrademe.com", expiry,
251 include_subdomains);
252 }
253
254 virtual ~MockURLRequestContext() {}
255
256 private:
257 TransportSecurityState transport_security_state_;
258 };
259
260 class MockHttpTransactionFactory : public HttpTransactionFactory {
261 public:
262 MockHttpTransactionFactory(NextProto next_proto, OrderedSocketData* data) {
263 data_ = data;
264 MockConnect connect_data(SYNCHRONOUS, OK);
265 data_->set_connect_data(connect_data);
266 session_deps_.reset(new SpdySessionDependencies(next_proto));
267 session_deps_->socket_factory->AddSocketDataProvider(data_);
268 http_session_ =
269 SpdySessionDependencies::SpdyCreateSession(session_deps_.get());
270 host_port_pair_.set_host("example.com");
271 host_port_pair_.set_port(80);
272 spdy_session_key_ = SpdySessionKey(host_port_pair_,
273 ProxyServer::Direct(),
274 kPrivacyModeDisabled);
275 session_ = CreateInsecureSpdySession(
276 http_session_, spdy_session_key_, BoundNetLog());
277 }
278
279 virtual int CreateTransaction(
280 RequestPriority priority,
281 scoped_ptr<HttpTransaction>* trans,
282 HttpTransactionDelegate* delegate) OVERRIDE {
283 NOTREACHED();
284 return ERR_UNEXPECTED;
285 }
286
287 virtual HttpCache* GetCache() OVERRIDE {
288 NOTREACHED();
289 return NULL;
290 }
291
292 virtual HttpNetworkSession* GetSession() OVERRIDE {
293 return http_session_.get();
294 }
295
296 private:
297 OrderedSocketData* data_;
298 scoped_ptr<SpdySessionDependencies> session_deps_;
299 scoped_refptr<HttpNetworkSession> http_session_;
300 base::WeakPtr<SpdySession> session_;
301 HostPortPair host_port_pair_;
302 SpdySessionKey spdy_session_key_;
303 };
304
305 } // namespace
306
307 class WebSocketJobTest : public PlatformTest,
308 public ::testing::WithParamInterface<NextProto> {
309 public:
310 WebSocketJobTest() : spdy_util_(GetParam()) {}
311
312 virtual void SetUp() OVERRIDE {
313 stream_type_ = STREAM_INVALID;
314 cookie_store_ = new MockCookieStore;
315 context_.reset(new MockURLRequestContext(cookie_store_.get()));
316 }
317 virtual void TearDown() OVERRIDE {
318 cookie_store_ = NULL;
319 context_.reset();
320 websocket_ = NULL;
321 socket_ = NULL;
322 }
323 void DoSendRequest() {
324 EXPECT_TRUE(websocket_->SendData(kHandshakeRequestWithoutCookie,
325 kHandshakeRequestWithoutCookieLength));
326 }
327 void DoSendData() {
328 if (received_data().size() == kHandshakeResponseWithoutCookieLength)
329 websocket_->SendData(kDataHello, kDataHelloLength);
330 }
331 void DoSync() {
332 sync_test_callback_.callback().Run(OK);
333 }
334 int WaitForResult() {
335 return sync_test_callback_.WaitForResult();
336 }
337 protected:
338 enum StreamType {
339 STREAM_INVALID,
340 STREAM_MOCK_SOCKET,
341 STREAM_SOCKET,
342 STREAM_SPDY_WEBSOCKET,
343 };
344 enum ThrottlingOption {
345 THROTTLING_OFF,
346 THROTTLING_ON,
347 };
348 enum SpdyOption {
349 SPDY_OFF,
350 SPDY_ON,
351 };
352 void InitWebSocketJob(const GURL& url,
353 MockSocketStreamDelegate* delegate,
354 StreamType stream_type) {
355 DCHECK_NE(STREAM_INVALID, stream_type);
356 stream_type_ = stream_type;
357 websocket_ = new WebSocketJob(delegate);
358
359 if (stream_type == STREAM_MOCK_SOCKET)
360 socket_ = new MockSocketStream(url, websocket_.get());
361
362 if (stream_type == STREAM_SOCKET || stream_type == STREAM_SPDY_WEBSOCKET) {
363 if (stream_type == STREAM_SPDY_WEBSOCKET) {
364 http_factory_.reset(
365 new MockHttpTransactionFactory(GetParam(), data_.get()));
366 context_->set_http_transaction_factory(http_factory_.get());
367 }
368
369 ssl_config_service_ = new MockSSLConfigService();
370 context_->set_ssl_config_service(ssl_config_service_.get());
371 proxy_service_.reset(ProxyService::CreateDirect());
372 context_->set_proxy_service(proxy_service_.get());
373 host_resolver_.reset(new MockHostResolver);
374 context_->set_host_resolver(host_resolver_.get());
375
376 socket_ = new SocketStream(url, websocket_.get());
377 socket_factory_.reset(new MockClientSocketFactory);
378 DCHECK(data_.get());
379 socket_factory_->AddSocketDataProvider(data_.get());
380 socket_->SetClientSocketFactory(socket_factory_.get());
381 }
382
383 websocket_->InitSocketStream(socket_.get());
384 websocket_->set_context(context_.get());
385 // MockHostResolver resolves all hosts to 127.0.0.1; however, when we create
386 // a WebSocketJob purely to block another one in a throttling test, we don't
387 // perform a real connect. In that case, the following address is used
388 // instead.
389 IPAddressNumber ip;
390 ParseIPLiteralToNumber("127.0.0.1", &ip);
391 websocket_->addresses_ = AddressList::CreateFromIPAddress(ip, 80);
392 }
393 void SkipToConnecting() {
394 websocket_->state_ = WebSocketJob::CONNECTING;
395 ASSERT_TRUE(WebSocketThrottle::GetInstance()->PutInQueue(websocket_.get()));
396 }
397 WebSocketJob::State GetWebSocketJobState() {
398 return websocket_->state_;
399 }
400 void CloseWebSocketJob() {
401 if (websocket_->socket_.get()) {
402 websocket_->socket_->DetachDelegate();
403 WebSocketThrottle::GetInstance()->RemoveFromQueue(websocket_.get());
404 }
405 websocket_->state_ = WebSocketJob::CLOSED;
406 websocket_->delegate_ = NULL;
407 websocket_->socket_ = NULL;
408 }
409 SocketStream* GetSocket(SocketStreamJob* job) {
410 return job->socket_.get();
411 }
412 const std::string& sent_data() const {
413 DCHECK_EQ(STREAM_MOCK_SOCKET, stream_type_);
414 MockSocketStream* socket =
415 static_cast<MockSocketStream*>(socket_.get());
416 DCHECK(socket);
417 return socket->sent_data();
418 }
419 const std::string& received_data() const {
420 DCHECK_NE(STREAM_INVALID, stream_type_);
421 MockSocketStreamDelegate* delegate =
422 static_cast<MockSocketStreamDelegate*>(websocket_->delegate_);
423 DCHECK(delegate);
424 return delegate->received_data();
425 }
426
427 void TestSimpleHandshake();
428 void TestSlowHandshake();
429 void TestHandshakeWithCookie();
430 void TestHandshakeWithCookieButNotAllowed();
431 void TestHSTSUpgrade();
432 void TestInvalidSendData();
433 void TestConnectByWebSocket(ThrottlingOption throttling);
434 void TestConnectBySpdy(SpdyOption spdy, ThrottlingOption throttling);
435 void TestThrottlingLimit();
436
437 SpdyWebSocketTestUtil spdy_util_;
438 StreamType stream_type_;
439 scoped_refptr<MockCookieStore> cookie_store_;
440 scoped_ptr<MockURLRequestContext> context_;
441 scoped_refptr<WebSocketJob> websocket_;
442 scoped_refptr<SocketStream> socket_;
443 scoped_ptr<MockClientSocketFactory> socket_factory_;
444 scoped_ptr<OrderedSocketData> data_;
445 TestCompletionCallback sync_test_callback_;
446 scoped_refptr<MockSSLConfigService> ssl_config_service_;
447 scoped_ptr<ProxyService> proxy_service_;
448 scoped_ptr<MockHostResolver> host_resolver_;
449 scoped_ptr<MockHttpTransactionFactory> http_factory_;
450
451 static const char kHandshakeRequestWithoutCookie[];
452 static const char kHandshakeRequestWithCookie[];
453 static const char kHandshakeRequestWithFilteredCookie[];
454 static const char kHandshakeResponseWithoutCookie[];
455 static const char kHandshakeResponseWithCookie[];
456 static const char kDataHello[];
457 static const char kDataWorld[];
458 static const char* const kHandshakeRequestForSpdy[];
459 static const char* const kHandshakeResponseForSpdy[];
460 static const size_t kHandshakeRequestWithoutCookieLength;
461 static const size_t kHandshakeRequestWithCookieLength;
462 static const size_t kHandshakeRequestWithFilteredCookieLength;
463 static const size_t kHandshakeResponseWithoutCookieLength;
464 static const size_t kHandshakeResponseWithCookieLength;
465 static const size_t kDataHelloLength;
466 static const size_t kDataWorldLength;
467 };
468
469 const char WebSocketJobTest::kHandshakeRequestWithoutCookie[] =
470 "GET /demo HTTP/1.1\r\n"
471 "Host: example.com\r\n"
472 "Upgrade: WebSocket\r\n"
473 "Connection: Upgrade\r\n"
474 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
475 "Origin: http://example.com\r\n"
476 "Sec-WebSocket-Protocol: sample\r\n"
477 "Sec-WebSocket-Version: 13\r\n"
478 "\r\n";
479
480 const char WebSocketJobTest::kHandshakeRequestWithCookie[] =
481 "GET /demo HTTP/1.1\r\n"
482 "Host: example.com\r\n"
483 "Upgrade: WebSocket\r\n"
484 "Connection: Upgrade\r\n"
485 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
486 "Origin: http://example.com\r\n"
487 "Sec-WebSocket-Protocol: sample\r\n"
488 "Sec-WebSocket-Version: 13\r\n"
489 "Cookie: WK-test=1\r\n"
490 "\r\n";
491
492 const char WebSocketJobTest::kHandshakeRequestWithFilteredCookie[] =
493 "GET /demo HTTP/1.1\r\n"
494 "Host: example.com\r\n"
495 "Upgrade: WebSocket\r\n"
496 "Connection: Upgrade\r\n"
497 "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
498 "Origin: http://example.com\r\n"
499 "Sec-WebSocket-Protocol: sample\r\n"
500 "Sec-WebSocket-Version: 13\r\n"
501 "Cookie: CR-test=1; CR-test-httponly=1\r\n"
502 "\r\n";
503
504 const char WebSocketJobTest::kHandshakeResponseWithoutCookie[] =
505 "HTTP/1.1 101 Switching Protocols\r\n"
506 "Upgrade: websocket\r\n"
507 "Connection: Upgrade\r\n"
508 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
509 "Sec-WebSocket-Protocol: sample\r\n"
510 "\r\n";
511
512 const char WebSocketJobTest::kHandshakeResponseWithCookie[] =
513 "HTTP/1.1 101 Switching Protocols\r\n"
514 "Upgrade: websocket\r\n"
515 "Connection: Upgrade\r\n"
516 "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
517 "Sec-WebSocket-Protocol: sample\r\n"
518 "Set-Cookie: CR-set-test=1\r\n"
519 "\r\n";
520
521 const char WebSocketJobTest::kDataHello[] = "Hello, ";
522
523 const char WebSocketJobTest::kDataWorld[] = "World!\n";
524
525 const size_t WebSocketJobTest::kHandshakeRequestWithoutCookieLength =
526 arraysize(kHandshakeRequestWithoutCookie) - 1;
527 const size_t WebSocketJobTest::kHandshakeRequestWithCookieLength =
528 arraysize(kHandshakeRequestWithCookie) - 1;
529 const size_t WebSocketJobTest::kHandshakeRequestWithFilteredCookieLength =
530 arraysize(kHandshakeRequestWithFilteredCookie) - 1;
531 const size_t WebSocketJobTest::kHandshakeResponseWithoutCookieLength =
532 arraysize(kHandshakeResponseWithoutCookie) - 1;
533 const size_t WebSocketJobTest::kHandshakeResponseWithCookieLength =
534 arraysize(kHandshakeResponseWithCookie) - 1;
535 const size_t WebSocketJobTest::kDataHelloLength =
536 arraysize(kDataHello) - 1;
537 const size_t WebSocketJobTest::kDataWorldLength =
538 arraysize(kDataWorld) - 1;
539
540 void WebSocketJobTest::TestSimpleHandshake() {
541 GURL url("ws://example.com/demo");
542 MockSocketStreamDelegate delegate;
543 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
544 SkipToConnecting();
545
546 DoSendRequest();
547 base::MessageLoop::current()->RunUntilIdle();
548 EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data());
549 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
550 websocket_->OnSentData(socket_.get(),
551 kHandshakeRequestWithoutCookieLength);
552 EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent());
553
554 websocket_->OnReceivedData(socket_.get(),
555 kHandshakeResponseWithoutCookie,
556 kHandshakeResponseWithoutCookieLength);
557 base::MessageLoop::current()->RunUntilIdle();
558 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
559 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
560 CloseWebSocketJob();
561 }
562
563 void WebSocketJobTest::TestSlowHandshake() {
564 GURL url("ws://example.com/demo");
565 MockSocketStreamDelegate delegate;
566 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
567 SkipToConnecting();
568
569 DoSendRequest();
570 // We assume request is sent in one data chunk (from WebKit)
571 // We don't support streaming request.
572 base::MessageLoop::current()->RunUntilIdle();
573 EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data());
574 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
575 websocket_->OnSentData(socket_.get(),
576 kHandshakeRequestWithoutCookieLength);
577 EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent());
578
579 std::vector<std::string> lines;
580 base::SplitString(kHandshakeResponseWithoutCookie, '\n', &lines);
581 for (size_t i = 0; i < lines.size() - 2; i++) {
582 std::string line = lines[i] + "\r\n";
583 SCOPED_TRACE("Line: " + line);
584 websocket_->OnReceivedData(socket_.get(), line.c_str(), line.size());
585 base::MessageLoop::current()->RunUntilIdle();
586 EXPECT_TRUE(delegate.received_data().empty());
587 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
588 }
589 websocket_->OnReceivedData(socket_.get(), "\r\n", 2);
590 base::MessageLoop::current()->RunUntilIdle();
591 EXPECT_FALSE(delegate.received_data().empty());
592 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
593 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
594 CloseWebSocketJob();
595 }
596
597 INSTANTIATE_TEST_CASE_P(
598 NextProto,
599 WebSocketJobTest,
600 testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
601 kProtoHTTP2Draft04));
602
603 TEST_P(WebSocketJobTest, DelayedCookies) {
604 WebSocketJob::set_websocket_over_spdy_enabled(true);
605 GURL url("ws://example.com/demo");
606 GURL cookieUrl("http://example.com/demo");
607 CookieOptions cookie_options;
608 scoped_refptr<DelayedCookieMonster> cookie_store = new DelayedCookieMonster();
609 context_->set_cookie_store(cookie_store.get());
610 cookie_store->SetCookieWithOptionsAsync(cookieUrl,
611 "CR-test=1",
612 cookie_options,
613 CookieMonster::SetCookiesCallback());
614 cookie_options.set_include_httponly();
615 cookie_store->SetCookieWithOptionsAsync(
616 cookieUrl, "CR-test-httponly=1", cookie_options,
617 CookieMonster::SetCookiesCallback());
618
619 MockSocketStreamDelegate delegate;
620 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
621 SkipToConnecting();
622
623 bool sent = websocket_->SendData(kHandshakeRequestWithCookie,
624 kHandshakeRequestWithCookieLength);
625 EXPECT_TRUE(sent);
626 base::MessageLoop::current()->RunUntilIdle();
627 EXPECT_EQ(kHandshakeRequestWithFilteredCookie, sent_data());
628 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
629 websocket_->OnSentData(socket_.get(),
630 kHandshakeRequestWithFilteredCookieLength);
631 EXPECT_EQ(kHandshakeRequestWithCookieLength,
632 delegate.amount_sent());
633
634 websocket_->OnReceivedData(socket_.get(),
635 kHandshakeResponseWithCookie,
636 kHandshakeResponseWithCookieLength);
637 base::MessageLoop::current()->RunUntilIdle();
638 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
639 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
640
641 CloseWebSocketJob();
642 }
643
644 void WebSocketJobTest::TestHandshakeWithCookie() {
645 GURL url("ws://example.com/demo");
646 GURL cookieUrl("http://example.com/demo");
647 CookieOptions cookie_options;
648 cookie_store_->SetCookieWithOptions(
649 cookieUrl, "CR-test=1", cookie_options);
650 cookie_options.set_include_httponly();
651 cookie_store_->SetCookieWithOptions(
652 cookieUrl, "CR-test-httponly=1", cookie_options);
653
654 MockSocketStreamDelegate delegate;
655 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
656 SkipToConnecting();
657
658 bool sent = websocket_->SendData(kHandshakeRequestWithCookie,
659 kHandshakeRequestWithCookieLength);
660 EXPECT_TRUE(sent);
661 base::MessageLoop::current()->RunUntilIdle();
662 EXPECT_EQ(kHandshakeRequestWithFilteredCookie, sent_data());
663 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
664 websocket_->OnSentData(socket_.get(),
665 kHandshakeRequestWithFilteredCookieLength);
666 EXPECT_EQ(kHandshakeRequestWithCookieLength,
667 delegate.amount_sent());
668
669 websocket_->OnReceivedData(socket_.get(),
670 kHandshakeResponseWithCookie,
671 kHandshakeResponseWithCookieLength);
672 base::MessageLoop::current()->RunUntilIdle();
673 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
674 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
675
676 EXPECT_EQ(3U, cookie_store_->entries().size());
677 EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url);
678 EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line);
679 EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url);
680 EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line);
681 EXPECT_EQ(cookieUrl, cookie_store_->entries()[2].url);
682 EXPECT_EQ("CR-set-test=1", cookie_store_->entries()[2].cookie_line);
683
684 CloseWebSocketJob();
685 }
686
687 void WebSocketJobTest::TestHandshakeWithCookieButNotAllowed() {
688 GURL url("ws://example.com/demo");
689 GURL cookieUrl("http://example.com/demo");
690 CookieOptions cookie_options;
691 cookie_store_->SetCookieWithOptions(
692 cookieUrl, "CR-test=1", cookie_options);
693 cookie_options.set_include_httponly();
694 cookie_store_->SetCookieWithOptions(
695 cookieUrl, "CR-test-httponly=1", cookie_options);
696
697 MockSocketStreamDelegate delegate;
698 delegate.set_allow_all_cookies(false);
699 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
700 SkipToConnecting();
701
702 bool sent = websocket_->SendData(kHandshakeRequestWithCookie,
703 kHandshakeRequestWithCookieLength);
704 EXPECT_TRUE(sent);
705 base::MessageLoop::current()->RunUntilIdle();
706 EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data());
707 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
708 websocket_->OnSentData(socket_.get(), kHandshakeRequestWithoutCookieLength);
709 EXPECT_EQ(kHandshakeRequestWithCookieLength, delegate.amount_sent());
710
711 websocket_->OnReceivedData(socket_.get(),
712 kHandshakeResponseWithCookie,
713 kHandshakeResponseWithCookieLength);
714 base::MessageLoop::current()->RunUntilIdle();
715 EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
716 EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
717
718 EXPECT_EQ(2U, cookie_store_->entries().size());
719 EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url);
720 EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line);
721 EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url);
722 EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line);
723
724 CloseWebSocketJob();
725 }
726
727 void WebSocketJobTest::TestHSTSUpgrade() {
728 GURL url("ws://upgrademe.com/");
729 MockSocketStreamDelegate delegate;
730 scoped_refptr<SocketStreamJob> job =
731 SocketStreamJob::CreateSocketStreamJob(
732 url, &delegate, context_->transport_security_state(),
733 context_->ssl_config_service());
734 EXPECT_TRUE(GetSocket(job.get())->is_secure());
735 job->DetachDelegate();
736
737 url = GURL("ws://donotupgrademe.com/");
738 job = SocketStreamJob::CreateSocketStreamJob(
739 url, &delegate, context_->transport_security_state(),
740 context_->ssl_config_service());
741 EXPECT_FALSE(GetSocket(job.get())->is_secure());
742 job->DetachDelegate();
743 }
744
745 void WebSocketJobTest::TestInvalidSendData() {
746 GURL url("ws://example.com/demo");
747 MockSocketStreamDelegate delegate;
748 InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
749 SkipToConnecting();
750
751 DoSendRequest();
752 // We assume request is sent in one data chunk (from WebKit)
753 // We don't support streaming request.
754 base::MessageLoop::current()->RunUntilIdle();
755 EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data());
756 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
757 websocket_->OnSentData(socket_.get(),
758 kHandshakeRequestWithoutCookieLength);
759 EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent());
760
761 // We could not send any data until connection is established.
762 bool sent = websocket_->SendData(kHandshakeRequestWithoutCookie,
763 kHandshakeRequestWithoutCookieLength);
764 EXPECT_FALSE(sent);
765 EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
766 CloseWebSocketJob();
767 }
768
769 // Following tests verify cooperation between WebSocketJob and SocketStream.
770 // Other former tests use MockSocketStream as SocketStream, so we could not
771 // check SocketStream behavior.
772 // OrderedSocketData provide socket level verifiation by checking out-going
773 // packets in comparison with the MockWrite array and emulating in-coming
774 // packets with MockRead array.
775
776 void WebSocketJobTest::TestConnectByWebSocket(
777 ThrottlingOption throttling) {
778 // This is a test for verifying cooperation between WebSocketJob and
779 // SocketStream. If |throttling| was |THROTTLING_OFF|, it test basic
780 // situation. If |throttling| was |THROTTLING_ON|, throttling limits the
781 // latter connection.
782 MockWrite writes[] = {
783 MockWrite(ASYNC,
784 kHandshakeRequestWithoutCookie,
785 kHandshakeRequestWithoutCookieLength,
786 1),
787 MockWrite(ASYNC,
788 kDataHello,
789 kDataHelloLength,
790 3)
791 };
792 MockRead reads[] = {
793 MockRead(ASYNC,
794 kHandshakeResponseWithoutCookie,
795 kHandshakeResponseWithoutCookieLength,
796 2),
797 MockRead(ASYNC,
798 kDataWorld,
799 kDataWorldLength,
800 4),
801 MockRead(SYNCHRONOUS, 0, 5) // EOF
802 };
803 data_.reset(new OrderedSocketData(
804 reads, arraysize(reads), writes, arraysize(writes)));
805
806 GURL url("ws://example.com/demo");
807 MockSocketStreamDelegate delegate;
808 WebSocketJobTest* test = this;
809 if (throttling == THROTTLING_ON)
810 delegate.SetOnStartOpenConnection(
811 base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test)));
812 delegate.SetOnConnected(
813 base::Bind(&WebSocketJobTest::DoSendRequest,
814 base::Unretained(test)));
815 delegate.SetOnReceivedData(
816 base::Bind(&WebSocketJobTest::DoSendData, base::Unretained(test)));
817 delegate.SetOnClose(
818 base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test)));
819 InitWebSocketJob(url, &delegate, STREAM_SOCKET);
820
821 scoped_refptr<WebSocketJob> block_websocket;
822 if (throttling == THROTTLING_ON) {
823 // Create former WebSocket object which obstructs the latter one.
824 block_websocket = new WebSocketJob(NULL);
825 block_websocket->addresses_ = AddressList(websocket_->address_list());
826 ASSERT_TRUE(
827 WebSocketThrottle::GetInstance()->PutInQueue(block_websocket.get()));
828 }
829
830 websocket_->Connect();
831
832 if (throttling == THROTTLING_ON) {
833 EXPECT_EQ(OK, WaitForResult());
834 EXPECT_TRUE(websocket_->IsWaiting());
835
836 // Remove the former WebSocket object from throttling queue to unblock the
837 // latter.
838 block_websocket->state_ = WebSocketJob::CLOSED;
839 WebSocketThrottle::GetInstance()->RemoveFromQueue(block_websocket.get());
840 block_websocket = NULL;
841 }
842
843 EXPECT_EQ(OK, WaitForResult());
844 EXPECT_TRUE(data_->at_read_eof());
845 EXPECT_TRUE(data_->at_write_eof());
846 EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState());
847 }
848
849 void WebSocketJobTest::TestConnectBySpdy(
850 SpdyOption spdy, ThrottlingOption throttling) {
851 // This is a test for verifying cooperation between WebSocketJob and
852 // SocketStream in the situation we have SPDY session to the server. If
853 // |throttling| was |THROTTLING_ON|, throttling limits the latter connection.
854 // If you enabled spdy, you should specify |spdy| as |SPDY_ON|. Expected
855 // results depend on its configuration.
856 MockWrite writes_websocket[] = {
857 MockWrite(ASYNC,
858 kHandshakeRequestWithoutCookie,
859 kHandshakeRequestWithoutCookieLength,
860 1),
861 MockWrite(ASYNC,
862 kDataHello,
863 kDataHelloLength,
864 3)
865 };
866 MockRead reads_websocket[] = {
867 MockRead(ASYNC,
868 kHandshakeResponseWithoutCookie,
869 kHandshakeResponseWithoutCookieLength,
870 2),
871 MockRead(ASYNC,
872 kDataWorld,
873 kDataWorldLength,
874 4),
875 MockRead(SYNCHRONOUS, 0, 5) // EOF
876 };
877
878 scoped_ptr<SpdyHeaderBlock> request_headers(new SpdyHeaderBlock());
879 spdy_util_.SetHeader("path", "/demo", request_headers.get());
880 spdy_util_.SetHeader("version", "WebSocket/13", request_headers.get());
881 spdy_util_.SetHeader("scheme", "ws", request_headers.get());
882 spdy_util_.SetHeader("host", "example.com", request_headers.get());
883 spdy_util_.SetHeader("origin", "http://example.com", request_headers.get());
884 spdy_util_.SetHeader("sec-websocket-protocol", "sample",
885 request_headers.get());
886
887 scoped_ptr<SpdyHeaderBlock> response_headers(new SpdyHeaderBlock());
888 spdy_util_.SetHeader("status", "101 Switching Protocols",
889 response_headers.get());
890 spdy_util_.SetHeader("sec-websocket-protocol", "sample",
891 response_headers.get());
892
893 const SpdyStreamId kStreamId = 1;
894 scoped_ptr<SpdyFrame> request_frame(
895 spdy_util_.ConstructSpdyWebSocketHandshakeRequestFrame(
896 request_headers.Pass(),
897 kStreamId,
898 MEDIUM));
899 scoped_ptr<SpdyFrame> response_frame(
900 spdy_util_.ConstructSpdyWebSocketHandshakeResponseFrame(
901 response_headers.Pass(),
902 kStreamId,
903 MEDIUM));
904 scoped_ptr<SpdyFrame> data_hello_frame(
905 spdy_util_.ConstructSpdyWebSocketDataFrame(
906 kDataHello,
907 kDataHelloLength,
908 kStreamId,
909 false));
910 scoped_ptr<SpdyFrame> data_world_frame(
911 spdy_util_.ConstructSpdyWebSocketDataFrame(
912 kDataWorld,
913 kDataWorldLength,
914 kStreamId,
915 false));
916 MockWrite writes_spdy[] = {
917 CreateMockWrite(*request_frame.get(), 1),
918 CreateMockWrite(*data_hello_frame.get(), 3),
919 };
920 MockRead reads_spdy[] = {
921 CreateMockRead(*response_frame.get(), 2),
922 CreateMockRead(*data_world_frame.get(), 4),
923 MockRead(SYNCHRONOUS, 0, 5) // EOF
924 };
925
926 if (spdy == SPDY_ON)
927 data_.reset(new OrderedSocketData(
928 reads_spdy, arraysize(reads_spdy),
929 writes_spdy, arraysize(writes_spdy)));
930 else
931 data_.reset(new OrderedSocketData(
932 reads_websocket, arraysize(reads_websocket),
933 writes_websocket, arraysize(writes_websocket)));
934
935 GURL url("ws://example.com/demo");
936 MockSocketStreamDelegate delegate;
937 WebSocketJobTest* test = this;
938 if (throttling == THROTTLING_ON)
939 delegate.SetOnStartOpenConnection(
940 base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test)));
941 delegate.SetOnConnected(
942 base::Bind(&WebSocketJobTest::DoSendRequest,
943 base::Unretained(test)));
944 delegate.SetOnReceivedData(
945 base::Bind(&WebSocketJobTest::DoSendData, base::Unretained(test)));
946 delegate.SetOnClose(
947 base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test)));
948 InitWebSocketJob(url, &delegate, STREAM_SPDY_WEBSOCKET);
949
950 scoped_refptr<WebSocketJob> block_websocket;
951 if (throttling == THROTTLING_ON) {
952 // Create former WebSocket object which obstructs the latter one.
953 block_websocket = new WebSocketJob(NULL);
954 block_websocket->addresses_ = AddressList(websocket_->address_list());
955 ASSERT_TRUE(
956 WebSocketThrottle::GetInstance()->PutInQueue(block_websocket.get()));
957 }
958
959 websocket_->Connect();
960
961 if (throttling == THROTTLING_ON) {
962 EXPECT_EQ(OK, WaitForResult());
963 EXPECT_TRUE(websocket_->IsWaiting());
964
965 // Remove the former WebSocket object from throttling queue to unblock the
966 // latter.
967 block_websocket->state_ = WebSocketJob::CLOSED;
968 WebSocketThrottle::GetInstance()->RemoveFromQueue(block_websocket.get());
969 block_websocket = NULL;
970 }
971
972 EXPECT_EQ(OK, WaitForResult());
973 EXPECT_TRUE(data_->at_read_eof());
974 EXPECT_TRUE(data_->at_write_eof());
975 EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState());
976 }
977
978 void WebSocketJobTest::TestThrottlingLimit() {
979 std::vector<scoped_refptr<WebSocketJob> > jobs;
980 const int kMaxWebSocketJobsThrottled = 1024;
981 IPAddressNumber ip;
982 ParseIPLiteralToNumber("127.0.0.1", &ip);
983 for (int i = 0; i < kMaxWebSocketJobsThrottled + 1; ++i) {
984 scoped_refptr<WebSocketJob> job = new WebSocketJob(NULL);
985 job->addresses_ = AddressList(AddressList::CreateFromIPAddress(ip, 80));
986 if (i >= kMaxWebSocketJobsThrottled)
987 EXPECT_FALSE(WebSocketThrottle::GetInstance()->PutInQueue(job));
988 else
989 EXPECT_TRUE(WebSocketThrottle::GetInstance()->PutInQueue(job));
990 jobs.push_back(job);
991 }
992
993 // Close the jobs in reverse order. Otherwise, We need to make them prepared
994 // for Wakeup call.
995 for (std::vector<scoped_refptr<WebSocketJob> >::reverse_iterator iter =
996 jobs.rbegin();
997 iter != jobs.rend();
998 ++iter) {
999 WebSocketJob* job = (*iter).get();
1000 job->state_ = WebSocketJob::CLOSED;
1001 WebSocketThrottle::GetInstance()->RemoveFromQueue(job);
1002 }
1003 }
1004
1005 // Execute tests in both spdy-disabled mode and spdy-enabled mode.
1006 TEST_P(WebSocketJobTest, SimpleHandshake) {
1007 WebSocketJob::set_websocket_over_spdy_enabled(false);
1008 TestSimpleHandshake();
1009 }
1010
1011 TEST_P(WebSocketJobTest, SlowHandshake) {
1012 WebSocketJob::set_websocket_over_spdy_enabled(false);
1013 TestSlowHandshake();
1014 }
1015
1016 TEST_P(WebSocketJobTest, HandshakeWithCookie) {
1017 WebSocketJob::set_websocket_over_spdy_enabled(false);
1018 TestHandshakeWithCookie();
1019 }
1020
1021 TEST_P(WebSocketJobTest, HandshakeWithCookieButNotAllowed) {
1022 WebSocketJob::set_websocket_over_spdy_enabled(false);
1023 TestHandshakeWithCookieButNotAllowed();
1024 }
1025
1026 TEST_P(WebSocketJobTest, HSTSUpgrade) {
1027 WebSocketJob::set_websocket_over_spdy_enabled(false);
1028 TestHSTSUpgrade();
1029 }
1030
1031 TEST_P(WebSocketJobTest, InvalidSendData) {
1032 WebSocketJob::set_websocket_over_spdy_enabled(false);
1033 TestInvalidSendData();
1034 }
1035
1036 TEST_P(WebSocketJobTest, SimpleHandshakeSpdyEnabled) {
1037 WebSocketJob::set_websocket_over_spdy_enabled(true);
1038 TestSimpleHandshake();
1039 }
1040
1041 TEST_P(WebSocketJobTest, SlowHandshakeSpdyEnabled) {
1042 WebSocketJob::set_websocket_over_spdy_enabled(true);
1043 TestSlowHandshake();
1044 }
1045
1046 TEST_P(WebSocketJobTest, HandshakeWithCookieSpdyEnabled) {
1047 WebSocketJob::set_websocket_over_spdy_enabled(true);
1048 TestHandshakeWithCookie();
1049 }
1050
1051 TEST_P(WebSocketJobTest, HandshakeWithCookieButNotAllowedSpdyEnabled) {
1052 WebSocketJob::set_websocket_over_spdy_enabled(true);
1053 TestHandshakeWithCookieButNotAllowed();
1054 }
1055
1056 TEST_P(WebSocketJobTest, HSTSUpgradeSpdyEnabled) {
1057 WebSocketJob::set_websocket_over_spdy_enabled(true);
1058 TestHSTSUpgrade();
1059 }
1060
1061 TEST_P(WebSocketJobTest, InvalidSendDataSpdyEnabled) {
1062 WebSocketJob::set_websocket_over_spdy_enabled(true);
1063 TestInvalidSendData();
1064 }
1065
1066 TEST_P(WebSocketJobTest, ConnectByWebSocket) {
1067 WebSocketJob::set_websocket_over_spdy_enabled(false);
1068 TestConnectByWebSocket(THROTTLING_OFF);
1069 }
1070
1071 TEST_P(WebSocketJobTest, ConnectByWebSocketSpdyEnabled) {
1072 WebSocketJob::set_websocket_over_spdy_enabled(true);
1073 TestConnectByWebSocket(THROTTLING_OFF);
1074 }
1075
1076 TEST_P(WebSocketJobTest, ConnectBySpdy) {
1077 WebSocketJob::set_websocket_over_spdy_enabled(false);
1078 TestConnectBySpdy(SPDY_OFF, THROTTLING_OFF);
1079 }
1080
1081 TEST_P(WebSocketJobTest, ConnectBySpdySpdyEnabled) {
1082 WebSocketJob::set_websocket_over_spdy_enabled(true);
1083 TestConnectBySpdy(SPDY_ON, THROTTLING_OFF);
1084 }
1085
1086 TEST_P(WebSocketJobTest, ThrottlingWebSocket) {
1087 WebSocketJob::set_websocket_over_spdy_enabled(false);
1088 TestConnectByWebSocket(THROTTLING_ON);
1089 }
1090
1091 TEST_P(WebSocketJobTest, ThrottlingMaxNumberOfThrottledJobLimit) {
1092 TestThrottlingLimit();
1093 }
1094
1095 TEST_P(WebSocketJobTest, ThrottlingWebSocketSpdyEnabled) {
1096 WebSocketJob::set_websocket_over_spdy_enabled(true);
1097 TestConnectByWebSocket(THROTTLING_ON);
1098 }
1099
1100 TEST_P(WebSocketJobTest, ThrottlingSpdy) {
1101 WebSocketJob::set_websocket_over_spdy_enabled(false);
1102 TestConnectBySpdy(SPDY_OFF, THROTTLING_ON);
1103 }
1104
1105 TEST_P(WebSocketJobTest, ThrottlingSpdySpdyEnabled) {
1106 WebSocketJob::set_websocket_over_spdy_enabled(true);
1107 TestConnectBySpdy(SPDY_ON, THROTTLING_ON);
1108 }
1109
1110 // TODO(toyoshim): Add tests to verify throttling, SPDY stream limitation.
1111 // TODO(toyoshim,yutak): Add tests to verify closing handshake.
1112 } // namespace net
OLDNEW
« no previous file with comments | « net/websockets/websocket_job_test.cc ('k') | net/websockets/websocket_net_log_params_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698