Chromium Code Reviews| Index: net/websockets/websocket_end_to_end_test.cc |
| diff --git a/net/websockets/websocket_end_to_end_test.cc b/net/websockets/websocket_end_to_end_test.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..9108824d3bc02598051850b2899592df4d8caf5f |
| --- /dev/null |
| +++ b/net/websockets/websocket_end_to_end_test.cc |
| @@ -0,0 +1,321 @@ |
| +// Copyright 2014 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. |
| + |
| +// End-to-end tests for WebSocket. Usage guidelines: |
| +// There are at least 4 sets of end-to-end tests in the source tree: |
| +// 1) Blink layout tests. These are easy to write, and efficient because the |
| +// client and server processes are both re-used. They are easy to write as |
| +// they are just Javascript. They should be used for the majority of |
| +// functional tests. |
| +// 2) Browser tests in //chrome/browser/net/websocket_browsertest.cc. These |
| +// are inefficient because both the server and client are restarted for each |
| +// test. They are also hard to write and hard to debug. They should generally |
| +// only be used for testing UI-related features. |
| +// 3) PPAPI tests in //chrome/test/ppapi/ppapi_browsertest.cc. These are |
| +// very hard to write and debug, and should only be used for verifying the |
| +// operation of the Pepper API. |
| +// 4) These tests. They are somewhat inefficient because the server is restarted |
| +// for each test. They are well-suited for tests which require special server |
| +// configurations. |
| +// |
| +// Tests should generally not be duplicated in the different places, except for |
| +// regression tests and basic smoke tests. |
| + |
| +#include <string> |
| + |
| +#include "base/bind.h" |
| +#include "base/bind_helpers.h" |
| +#include "base/callback.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/message_loop/message_loop.h" |
| +#include "base/run_loop.h" |
| +#include "net/base/auth.h" |
| +#include "net/base/network_delegate.h" |
| +#include "net/base/test_data_directory.h" |
| +#include "net/proxy/proxy_service.h" |
| +#include "net/test/spawned_test_server/spawned_test_server.h" |
| +#include "net/url_request/url_request_test_util.h" |
| +#include "net/websockets/websocket_channel.h" |
| +#include "net/websockets/websocket_event_interface.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| +#include "url/origin.h" |
| + |
| +namespace net { |
| + |
| +namespace { |
| + |
| +static const char kEchoServer[] = "echo-with-no-extension"; |
| + |
| +// An implementation of WebSocketEventInterface that waits for and records the |
| +// results of the connect. |
| +class ConnectTestingEventInterface : public WebSocketEventInterface { |
| + public: |
| + ConnectTestingEventInterface() : fail_(true) {} |
| + |
| + void WaitForResponse() { run_loop_.Run(); } |
| + |
| + // fail() is true if the handshake failed. |
|
tyoshino (SeeGerritForStatus)
2015/01/19 03:59:48
also if FailChannel was called
Adam Rice
2015/01/19 04:50:14
Actually, it always means FailChannel was called.
|
| + bool fail() const { return fail_; } |
| + |
| + // Only set if the handshake failed, otherwise empty. |
| + // Failure messages appear on the console and are checked by the layout tests, |
| + // so they are expected to stay reasonably stable across versions. |
| + std::string failure_message() const { return failure_message_; } |
| + |
| + std::string selected_subprotocol() const { return selected_subprotocol_; } |
| + |
| + std::string extensions() const { return extensions_; } |
| + |
| + ChannelState OnAddChannelResponse(bool fail, |
| + const std::string& selected_subprotocol, |
| + const std::string& extensions) override { |
| + fail_ = fail; |
| + selected_subprotocol_ = selected_subprotocol; |
| + extensions_ = extensions; |
| + QuitNestedEventLoop(); |
| + return fail ? CHANNEL_DELETED : CHANNEL_ALIVE; |
| + } |
| + |
| + ChannelState OnDataFrame(bool fin, |
| + WebSocketMessageType type, |
| + const std::vector<char>& data) override { |
| + return CHANNEL_ALIVE; |
| + } |
| + |
| + ChannelState OnFlowControl(int64 quota) override { return CHANNEL_ALIVE; } |
| + |
| + ChannelState OnClosingHandshake() override { return CHANNEL_ALIVE; } |
| + |
| + ChannelState OnDropChannel(bool was_clean, |
| + uint16 code, |
| + const std::string& reason) override { |
| + return CHANNEL_DELETED; |
| + } |
| + |
| + ChannelState OnFailChannel(const std::string& message) override { |
| + fail_ = true; |
| + failure_message_ = message; |
| + QuitNestedEventLoop(); |
| + return CHANNEL_DELETED; |
| + } |
| + |
| + ChannelState OnStartOpeningHandshake( |
| + scoped_ptr<WebSocketHandshakeRequestInfo> request) override { |
| + return CHANNEL_ALIVE; |
| + } |
| + |
| + ChannelState OnFinishOpeningHandshake( |
| + scoped_ptr<WebSocketHandshakeResponseInfo> response) override { |
| + return CHANNEL_ALIVE; |
| + } |
| + |
| + ChannelState OnSSLCertificateError( |
| + scoped_ptr<SSLErrorCallbacks> ssl_error_callbacks, |
| + const GURL& url, |
| + const SSLInfo& ssl_info, |
| + bool fatal) override { |
| + base::MessageLoop::current()->PostTask( |
| + FROM_HERE, base::Bind(&SSLErrorCallbacks::CancelSSLRequest, |
| + base::Owned(ssl_error_callbacks.release()), |
| + ERR_SSL_PROTOCOL_ERROR, &ssl_info)); |
| + return CHANNEL_ALIVE; |
| + } |
| + |
| + private: |
| + void QuitNestedEventLoop() { run_loop_.Quit(); } |
| + |
| + bool fail_; |
| + std::string selected_subprotocol_; |
| + std::string extensions_; |
| + std::string failure_message_; |
| + base::RunLoop run_loop_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(ConnectTestingEventInterface); |
| +}; |
| + |
| +// A subclass of TestNetworkDelegate that additionally implements the |
| +// OnResolveProxy callback and records the information passed to it. |
| +class TestNetworkDelegateWithProxyInfo : public TestNetworkDelegate { |
| + public: |
| + TestNetworkDelegateWithProxyInfo() {} |
| + |
| + struct OnResolveProxyInfo { |
| + GURL url; |
| + ProxyInfo proxy_info; |
| + }; |
| + |
| + const OnResolveProxyInfo& on_resolve_proxy_info() const { |
| + return on_resolve_proxy_info_; |
| + } |
| + |
| + protected: |
| + void OnResolveProxy(const GURL& url, |
| + int load_flags, |
| + const ProxyService& proxy_service, |
| + ProxyInfo* result) override { |
| + on_resolve_proxy_info_.url = url; |
| + on_resolve_proxy_info_.proxy_info = *result; |
| + } |
| + |
| + private: |
| + OnResolveProxyInfo on_resolve_proxy_info_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(TestNetworkDelegateWithProxyInfo); |
| +}; |
| + |
| +class WebSocketEndToEndTest : public ::testing::Test { |
| + protected: |
| + WebSocketEndToEndTest() |
| + : event_interface_(new ConnectTestingEventInterface), |
| + network_delegate_(new TestNetworkDelegateWithProxyInfo), |
| + context_(true), |
| + channel_(make_scoped_ptr(event_interface_), &context_), |
| + initialised_context_(false) {} |
| + |
| + // Initialise the URLRequestContext. Normally done automatically by |
| + // ConnectAndWait(). This method is for the use of tests that need the |
| + // URLRequestContext initialised before calling ConnectAndWait(). |
| + void InitialiseContext() { |
| + context_.set_network_delegate(network_delegate_.get()); |
| + context_.Init(); |
| + initialised_context_ = true; |
| + } |
| + |
| + // Send the connect request to |socket_url| and wait for a response. Returns |
| + // true if the handshake succeeded. |
| + bool ConnectAndWait(const GURL& socket_url) { |
| + if (!initialised_context_) { |
| + InitialiseContext(); |
| + } |
| + std::vector<std::string> sub_protocols; |
| + url::Origin origin("http://localhost"); |
| + channel_.SendAddChannelRequest(GURL(socket_url), sub_protocols, origin); |
| + event_interface_->WaitForResponse(); |
| + return !event_interface_->fail(); |
| + } |
| + |
| + ConnectTestingEventInterface* event_interface_; // owned by channel_ |
| + scoped_ptr<TestNetworkDelegateWithProxyInfo> network_delegate_; |
| + TestURLRequestContext context_; |
| + WebSocketChannel channel_; |
| + bool initialised_context_; |
| +}; |
| + |
| +// None of these tests work on Android. |
| +// TODO(ricea): Make these tests work on Android. See crbug.com/441711. |
| +#if defined(OS_ANDROID) |
| +#define DISABLED_ON_ANDROID(test) DISABLED_##test |
| +#else |
| +#define DISABLED_ON_ANDROID(test) test |
| +#endif |
| + |
| +// Basic test of connectivity. If this test fails, nothing else can be expected |
| +// to work. |
| +TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(BasicSmokeTest)) { |
| + SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS, |
| + SpawnedTestServer::kLocalhost, |
| + GetWebSocketTestDataDirectory()); |
| + ASSERT_TRUE(ws_server.Start()); |
| + EXPECT_TRUE(ConnectAndWait(ws_server.GetURL(kEchoServer))); |
| +} |
| + |
| +// Test for issue crbug.com/433695 "Unencrypted WebSocket connection via |
| +// authenticated proxy times out" |
| +// TODO(ricea): Enable this when the issue is fixed. |
| +TEST_F(WebSocketEndToEndTest, DISABLED_HttpsProxyUnauthedFails) { |
| + SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY, |
| + SpawnedTestServer::kLocalhost, |
| + base::FilePath()); |
| + SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS, |
| + SpawnedTestServer::kLocalhost, |
| + GetWebSocketTestDataDirectory()); |
| + ASSERT_TRUE(proxy_server.StartInBackground()); |
| + ASSERT_TRUE(ws_server.StartInBackground()); |
| + ASSERT_TRUE(proxy_server.BlockUntilStarted()); |
| + ASSERT_TRUE(ws_server.BlockUntilStarted()); |
| + std::string proxy_config = |
| + "https=" + proxy_server.host_port_pair().ToString(); |
| + scoped_ptr<ProxyService> proxy_service( |
| + ProxyService::CreateFixed(proxy_config)); |
| + ASSERT_TRUE(proxy_service); |
| + context_.set_proxy_service(proxy_service.get()); |
| + EXPECT_FALSE(ConnectAndWait(ws_server.GetURL(kEchoServer))); |
| + EXPECT_EQ("Proxy authentication failed", event_interface_->failure_message()); |
| +} |
| + |
| +TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(HttpsWssProxyUnauthedFails)) { |
| + SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY, |
| + SpawnedTestServer::kLocalhost, |
| + base::FilePath()); |
| + SpawnedTestServer wss_server(SpawnedTestServer::TYPE_WSS, |
| + SpawnedTestServer::kLocalhost, |
| + GetWebSocketTestDataDirectory()); |
| + ASSERT_TRUE(proxy_server.StartInBackground()); |
| + ASSERT_TRUE(wss_server.StartInBackground()); |
| + ASSERT_TRUE(proxy_server.BlockUntilStarted()); |
| + ASSERT_TRUE(wss_server.BlockUntilStarted()); |
| + std::string proxy_config = |
| + "https=" + proxy_server.host_port_pair().ToString(); |
| + scoped_ptr<ProxyService> proxy_service( |
| + ProxyService::CreateFixed(proxy_config)); |
| + ASSERT_TRUE(proxy_service); |
| + context_.set_proxy_service(proxy_service.get()); |
| + EXPECT_FALSE(ConnectAndWait(wss_server.GetURL(kEchoServer))); |
| + EXPECT_EQ("Proxy authentication failed", event_interface_->failure_message()); |
| +} |
| + |
| +// Regression test for crbug/426736 "WebSocket connections not using configured |
| +// system HTTPS Proxy". |
| +TEST_F(WebSocketEndToEndTest, DISABLED_ON_ANDROID(HttpsProxyUsed)) { |
| + SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY, |
| + SpawnedTestServer::kLocalhost, |
| + base::FilePath()); |
| + SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS, |
| + SpawnedTestServer::kLocalhost, |
| + GetWebSocketTestDataDirectory()); |
| + ASSERT_TRUE(proxy_server.StartInBackground()); |
| + ASSERT_TRUE(ws_server.StartInBackground()); |
| + ASSERT_TRUE(proxy_server.BlockUntilStarted()); |
| + ASSERT_TRUE(ws_server.BlockUntilStarted()); |
| + std::string proxy_config = "https=" + |
| + proxy_server.host_port_pair().ToString() + ";" + |
| + "http=" + proxy_server.host_port_pair().ToString(); |
| + scoped_ptr<ProxyService> proxy_service( |
| + ProxyService::CreateFixed(proxy_config)); |
| + context_.set_proxy_service(proxy_service.get()); |
| + InitialiseContext(); |
| + |
| + // The test server doesn't have an unauthenticated proxy mode. WebSockets |
| + // cannot provide auth information that isn't already cached, so it's |
| + // necessary to preflight an HTTP request to authenticate against the proxy. |
| + std::string scheme("http"); |
| + GURL::Replacements replacements; |
| + replacements.SetSchemeStr(scheme); |
| + // It doesn't matter what the URL is, as long as it is an HTTP navigation. |
| + GURL http_page = |
| + ws_server.GetURL("connect_check.html").ReplaceComponents(replacements); |
| + TestDelegate delegate; |
| + delegate.set_credentials( |
| + AuthCredentials(base::ASCIIToUTF16("foo"), base::ASCIIToUTF16("bar"))); |
| + { |
| + scoped_ptr<URLRequest> request( |
| + context_.CreateRequest(http_page, DEFAULT_PRIORITY, &delegate, NULL)); |
| + request->Start(); |
| + // TestDelegate exits the message loop when the request completes by |
| + // default. |
| + base::RunLoop().Run(); |
| + EXPECT_TRUE(delegate.auth_required_called()); |
| + } |
| + |
| + GURL ws_url = ws_server.GetURL(kEchoServer); |
| + EXPECT_TRUE(ConnectAndWait(ws_url)); |
| + const TestNetworkDelegateWithProxyInfo::OnResolveProxyInfo& info = |
| + network_delegate_->on_resolve_proxy_info(); |
| + EXPECT_EQ(ws_url, info.url); |
| + EXPECT_TRUE(info.proxy_info.is_http()); |
| +} |
| + |
| +} // namespace |
| + |
| +} // namespace net |