diff options
author | hamaji@chromium.org <hamaji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-06 12:11:05 +0000 |
---|---|---|
committer | hamaji@chromium.org <hamaji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-06 12:11:05 +0000 |
commit | a692c6f3a59e711bc38fc72fce3c938deec64da0 (patch) | |
tree | 0583e80059992b55252390a7891e1651ba1c2ab3 | |
parent | 4bd778d6cd4e108a38caeda9b283f01691fe7ecf (diff) | |
download | chromium_src-a692c6f3a59e711bc38fc72fce3c938deec64da0.zip chromium_src-a692c6f3a59e711bc38fc72fce3c938deec64da0.tar.gz chromium_src-a692c6f3a59e711bc38fc72fce3c938deec64da0.tar.bz2 |
Reverting r22591. It seems to be making purify fail.
r22590: http://build.chromium.org/buildbot/waterfall/builders/XP%20Unit%20(purify)/builds/5026
r22591: http://build.chromium.org/buildbot/waterfall/builders/XP%20Unit%20(purify)/builds/5030
TEST=none
BUG=none
TBR=eroman
Review URL: http://codereview.chromium.org/165048
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@22603 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/net/resolve_proxy_msg_helper_unittest.cc | 408 | ||||
-rw-r--r-- | net/net.gyp | 1 | ||||
-rw-r--r-- | net/proxy/mock_proxy_resolver.h | 1375 | ||||
-rw-r--r-- | net/proxy/proxy_service_unittest.cc | 151 |
4 files changed, 1795 insertions, 140 deletions
diff --git a/chrome/browser/net/resolve_proxy_msg_helper_unittest.cc b/chrome/browser/net/resolve_proxy_msg_helper_unittest.cc index b9974ca..ef016d3 100644 --- a/chrome/browser/net/resolve_proxy_msg_helper_unittest.cc +++ b/chrome/browser/net/resolve_proxy_msg_helper_unittest.cc @@ -6,12 +6,13 @@ #include "base/waitable_event.h" #include "net/base/net_errors.h" -#include "net/proxy/mock_proxy_resolver.h" #include "net/proxy/proxy_config_service.h" +#include "net/proxy/proxy_resolver.h" +#include "net/proxy/single_threaded_proxy_resolver.h" #include "testing/gtest/include/gtest/gtest.h" // This ProxyConfigService always returns "http://pac" as the PAC url to use. -class MockProxyConfigService : public net::ProxyConfigService { +class MockProxyConfigService: public net::ProxyConfigService { public: virtual int GetProxyConfig(net::ProxyConfig* results) { results->pac_url = GURL("http://pac"); @@ -19,45 +20,232 @@ class MockProxyConfigService : public net::ProxyConfigService { } }; -class MyDelegate : public ResolveProxyMsgHelper::Delegate { +// This PAC resolver always returns the hostname of the query URL as the +// proxy to use. The Block() method will make GetProxyForURL() hang until +// Unblock() is called. +class SyncMockProxyResolver : public net::ProxyResolver { public: - struct PendingResult { - PendingResult(IPC::Message* msg, - int error_code, - const std::string& proxy_list) - : msg(msg), error_code(error_code), proxy_list(proxy_list) { - } - - IPC::Message* msg; - int error_code; - std::string proxy_list; - }; - - // ResolveProxyMsgHelper::Delegate implementation: + SyncMockProxyResolver() : ProxyResolver(false /*expects_pac_bytes*/), + event_(false, false), + is_blocked_(false) { + } + + virtual int GetProxyForURL(const GURL& query_url, + net::ProxyInfo* results, + net::CompletionCallback* callback, + RequestHandle* request) { + if (is_blocked_) + event_.Wait(); + results->UseNamedProxy(query_url.host()); + return net::OK; + } + + virtual void CancelRequest(RequestHandle request) { + NOTREACHED(); + } + + virtual int SetPacScript(const GURL& /*pac_url*/, + const std::string& /*bytes*/, + net::CompletionCallback* /*callback*/) { + return net::OK; + } + + void Block() { + is_blocked_ = true; + event_.Reset(); + } + + void Unblock() { + is_blocked_ = false; + event_.Signal(); + } + + private: + base::WaitableEvent event_; + bool is_blocked_; +}; + +class MockProxyResolver : public net::SingleThreadedProxyResolver { + public: + MockProxyResolver() + : net::SingleThreadedProxyResolver(new SyncMockProxyResolver) { + x = reinterpret_cast<SyncMockProxyResolver*>(resolver()); + } + + // TODO(eroman): cleanup. + SyncMockProxyResolver* x; +}; + +// This struct holds the values that were passed to +// Delegate::OnResolveProxyCompleted(). The caller should use WaitUntilDone() +// to block until the result has been populated. +struct ResultFuture { + public: + ResultFuture() + : reply_msg(NULL), + error_code(0), + started_(false, false), + completed_(false, false) { + } + + // Wait until the request has completed. In other words we have invoked: + // ResolveProxyMsgHelper::Delegate::OnResolveProxyCompleted. + void WaitUntilDone() { + completed_.Wait(); + } + + // Wait until the request has been sent to ResolveProxyMsgHelper. + void WaitUntilStarted() { + started_.Wait(); + } + + bool TimedWaitUntilDone(const base::TimeDelta& max_time) { + return completed_.TimedWait(max_time); + } + + // These fields are only valid after returning from WaitUntilDone(). + IPC::Message* reply_msg; + int error_code; + std::string proxy_list; + + private: + friend class AsyncRequestRunner; + base::WaitableEvent started_; + base::WaitableEvent completed_; +}; + +// This class lives on the io thread. It starts async requests using the +// class under test (ResolveProxyMsgHelper), and signals the result future on +// completion. +class AsyncRequestRunner : public ResolveProxyMsgHelper::Delegate { + public: + AsyncRequestRunner(net::ProxyService* proxy_service) { + resolve_proxy_msg_helper_.reset( + new ResolveProxyMsgHelper(this, proxy_service)); + } + + void Start(ResultFuture* future, const GURL& url, IPC::Message* reply_msg) { + futures_.push_back(future); + resolve_proxy_msg_helper_->Start(url, reply_msg); + + // Notify of request start. + future->started_.Signal(); + } + virtual void OnResolveProxyCompleted(IPC::Message* reply_msg, int error_code, const std::string& proxy_list) { - DCHECK(!pending_result_.get()); - pending_result_.reset(new PendingResult(reply_msg, error_code, proxy_list)); + // Update the result future for this request (top of queue), and signal it. + ResultFuture* future = futures_.front(); + futures_.pop_front(); + + future->reply_msg = reply_msg; + future->error_code = error_code; + future->proxy_list = proxy_list; + + // Notify of request completion. + future->completed_.Signal(); + } + + private: + std::deque<ResultFuture*> futures_; + scoped_ptr<ResolveProxyMsgHelper> resolve_proxy_msg_helper_; +}; + +// Helper class to start async requests on an io thread, and return a +// result future. The caller then uses ResultFuture::WaitUntilDone() to +// get at the results. It "bridges" the originating thread with the helper +// io thread. +class RunnerBridge { + public: + RunnerBridge() : io_thread_("io_thread"), done_(false, false) { + // Start an io thread where we will run the async requests. + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + io_thread_.StartWithOptions(options); + + // Construct the state that lives on io thread. + io_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &RunnerBridge::DoConstruct)); + done_.Wait(); } - const PendingResult* pending_result() const { return pending_result_.get(); } + // Start an async request on the io thread. + ResultFuture* Start(const GURL& url, IPC::Message* reply_msg) { + ResultFuture* future = new ResultFuture(); - void clear_pending_result() { - pending_result_.reset(); + io_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + async_runner_, &AsyncRequestRunner::Start, future, url, reply_msg)); + + return future; + } + + void DestroyAsyncRunner() { + io_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &RunnerBridge::DoDestroyAsyncRunner)); + done_.Wait(); + } + + ~RunnerBridge() { + io_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &RunnerBridge::DoDestroy)); + done_.Wait(); + } + + MockProxyResolver* proxy_resolver() { + return proxy_resolver_; + } + + // Called from io thread. + void DoConstruct() { + proxy_resolver_ = new MockProxyResolver(); + proxy_service_ = new net::ProxyService(new MockProxyConfigService(), + proxy_resolver_); + async_runner_ = new AsyncRequestRunner(proxy_service_); + done_.Signal(); + } + + // Called from io thread. + void DoDestroy() { + delete async_runner_; + delete proxy_service_; + done_.Signal(); + } + + // Called from io thread. + void DoDestroyAsyncRunner() { + delete async_runner_; + async_runner_ = NULL; + done_.Signal(); } private: - scoped_ptr<PendingResult> pending_result_; + base::Thread io_thread_; + base::WaitableEvent done_; + + net::ProxyService* proxy_service_; + MockProxyResolver* proxy_resolver_; // Owned by proxy_service_. + + AsyncRequestRunner* async_runner_; }; +// Avoid the need to have an AddRef / Release +template<> +void RunnableMethodTraits<RunnerBridge>::RetainCallee(RunnerBridge*) {} +template<> +void RunnableMethodTraits<RunnerBridge>::ReleaseCallee(RunnerBridge*) {} + +template<> +void RunnableMethodTraits<AsyncRequestRunner>::RetainCallee( + AsyncRequestRunner*) {} +template<> +void RunnableMethodTraits<AsyncRequestRunner>::ReleaseCallee( + AsyncRequestRunner*) {} + + // Issue three sequential requests -- each should succeed. TEST(ResolveProxyMsgHelperTest, Sequential) { - net::MockAsyncProxyResolver* resolver = new net::MockAsyncProxyResolver; - net::ProxyService service(new MockProxyConfigService, resolver); - - MyDelegate delegate; - ResolveProxyMsgHelper helper(&delegate, &service); + RunnerBridge runner; GURL url1("http://www.google1.com/"); GURL url2("http://www.google2.com/"); @@ -70,56 +258,33 @@ TEST(ResolveProxyMsgHelperTest, Sequential) { // Execute each request sequentially (so there are never 2 requests // outstanding at the same time). - helper.Start(url1, msg1.get()); - - // Finish ProxyService's initialization. - resolver->pending_set_pac_script_request()->CompleteNow(net::OK); + scoped_ptr<ResultFuture> result1(runner.Start(url1, msg1.get())); + result1->WaitUntilDone(); - ASSERT_EQ(1u, resolver->pending_requests().size()); - EXPECT_EQ(url1, resolver->pending_requests()[0]->url()); - resolver->pending_requests()[0]->results()->UseNamedProxy("result1:80"); - resolver->pending_requests()[0]->CompleteNow(net::OK); + scoped_ptr<ResultFuture> result2(runner.Start(url2, msg2.get())); + result2->WaitUntilDone(); - // Check result. - EXPECT_EQ(msg1.get(), delegate.pending_result()->msg); - EXPECT_EQ(net::OK, delegate.pending_result()->error_code); - EXPECT_EQ("PROXY result1:80", delegate.pending_result()->proxy_list); - delegate.clear_pending_result(); + scoped_ptr<ResultFuture> result3(runner.Start(url3, msg3.get())); + result3->WaitUntilDone(); - helper.Start(url2, msg2.get()); + // Check that each request gave the expected result. - ASSERT_EQ(1u, resolver->pending_requests().size()); - EXPECT_EQ(url2, resolver->pending_requests()[0]->url()); - resolver->pending_requests()[0]->results()->UseNamedProxy("result2:80"); - resolver->pending_requests()[0]->CompleteNow(net::OK); + EXPECT_EQ(msg1.get(), result1->reply_msg); + EXPECT_EQ(net::OK, result1->error_code); + EXPECT_EQ("PROXY www.google1.com:80", result1->proxy_list); - // Check result. - EXPECT_EQ(msg2.get(), delegate.pending_result()->msg); - EXPECT_EQ(net::OK, delegate.pending_result()->error_code); - EXPECT_EQ("PROXY result2:80", delegate.pending_result()->proxy_list); - delegate.clear_pending_result(); + EXPECT_EQ(msg2.get(), result2->reply_msg); + EXPECT_EQ(net::OK, result2->error_code); + EXPECT_EQ("PROXY www.google2.com:80", result2->proxy_list); - helper.Start(url3, msg3.get()); - - ASSERT_EQ(1u, resolver->pending_requests().size()); - EXPECT_EQ(url3, resolver->pending_requests()[0]->url()); - resolver->pending_requests()[0]->results()->UseNamedProxy("result3:80"); - resolver->pending_requests()[0]->CompleteNow(net::OK); - - // Check result. - EXPECT_EQ(msg3.get(), delegate.pending_result()->msg); - EXPECT_EQ(net::OK, delegate.pending_result()->error_code); - EXPECT_EQ("PROXY result3:80", delegate.pending_result()->proxy_list); - delegate.clear_pending_result(); + EXPECT_EQ(msg3.get(), result3->reply_msg); + EXPECT_EQ(net::OK, result3->error_code); + EXPECT_EQ("PROXY www.google3.com:80", result3->proxy_list); } // Issue a request while one is already in progress -- should be queued. TEST(ResolveProxyMsgHelperTest, QueueRequests) { - net::MockAsyncProxyResolver* resolver = new net::MockAsyncProxyResolver; - net::ProxyService service(new MockProxyConfigService, resolver); - - MyDelegate delegate; - ResolveProxyMsgHelper helper(&delegate, &service); + RunnerBridge runner; GURL url1("http://www.google1.com/"); GURL url2("http://www.google2.com/"); @@ -129,64 +294,44 @@ TEST(ResolveProxyMsgHelperTest, QueueRequests) { scoped_ptr<IPC::Message> msg2(new IPC::Message()); scoped_ptr<IPC::Message> msg3(new IPC::Message()); - // Start three requests. Since the proxy resolver is async, all the - // requests will be pending. - - helper.Start(url1, msg1.get()); - - // Finish ProxyService's initialization. - resolver->pending_set_pac_script_request()->CompleteNow(net::OK); + // Make the proxy resolver hang on the next request. + runner.proxy_resolver()->x->Block(); - helper.Start(url2, msg2.get()); - helper.Start(url3, msg3.get()); + // Start three requests. Since the proxy resolver is hung, the second two + // will be pending. - // ResolveProxyHelper only keeps 1 request outstanding in ProxyService - // at a time. - ASSERT_EQ(1u, resolver->pending_requests().size()); - EXPECT_EQ(url1, resolver->pending_requests()[0]->url()); + scoped_ptr<ResultFuture> result1(runner.Start(url1, msg1.get())); + scoped_ptr<ResultFuture> result2(runner.Start(url2, msg2.get())); + scoped_ptr<ResultFuture> result3(runner.Start(url3, msg3.get())); - resolver->pending_requests()[0]->results()->UseNamedProxy("result1:80"); - resolver->pending_requests()[0]->CompleteNow(net::OK); + // Wait for the final request to have been scheduled. Otherwise we may rush + // to calling Unblock() without actually having blocked anything. + result3->WaitUntilStarted(); - // Check result. - EXPECT_EQ(msg1.get(), delegate.pending_result()->msg); - EXPECT_EQ(net::OK, delegate.pending_result()->error_code); - EXPECT_EQ("PROXY result1:80", delegate.pending_result()->proxy_list); - delegate.clear_pending_result(); + // Unblock the proxy service so requests 1-3 can complete. + runner.proxy_resolver()->x->Unblock(); - ASSERT_EQ(1u, resolver->pending_requests().size()); - EXPECT_EQ(url2, resolver->pending_requests()[0]->url()); + // Wait for all the requests to finish (they run in FIFO order). + result3->WaitUntilDone(); - resolver->pending_requests()[0]->results()->UseNamedProxy("result2:80"); - resolver->pending_requests()[0]->CompleteNow(net::OK); + // Check that each call invoked the callback with the right parameters. - // Check result. - EXPECT_EQ(msg2.get(), delegate.pending_result()->msg); - EXPECT_EQ(net::OK, delegate.pending_result()->error_code); - EXPECT_EQ("PROXY result2:80", delegate.pending_result()->proxy_list); - delegate.clear_pending_result(); + EXPECT_EQ(msg1.get(), result1->reply_msg); + EXPECT_EQ(net::OK, result1->error_code); + EXPECT_EQ("PROXY www.google1.com:80", result1->proxy_list); - ASSERT_EQ(1u, resolver->pending_requests().size()); - EXPECT_EQ(url3, resolver->pending_requests()[0]->url()); + EXPECT_EQ(msg2.get(), result2->reply_msg); + EXPECT_EQ(net::OK, result2->error_code); + EXPECT_EQ("PROXY www.google2.com:80", result2->proxy_list); - resolver->pending_requests()[0]->results()->UseNamedProxy("result3:80"); - resolver->pending_requests()[0]->CompleteNow(net::OK); - - // Check result. - EXPECT_EQ(msg3.get(), delegate.pending_result()->msg); - EXPECT_EQ(net::OK, delegate.pending_result()->error_code); - EXPECT_EQ("PROXY result3:80", delegate.pending_result()->proxy_list); - delegate.clear_pending_result(); + EXPECT_EQ(msg3.get(), result3->reply_msg); + EXPECT_EQ(net::OK, result3->error_code); + EXPECT_EQ("PROXY www.google3.com:80", result3->proxy_list); } // Delete the helper while a request is in progress, and others are pending. TEST(ResolveProxyMsgHelperTest, CancelPendingRequests) { - net::MockAsyncProxyResolver* resolver = new net::MockAsyncProxyResolver; - net::ProxyService service(new MockProxyConfigService, resolver); - - MyDelegate delegate; - scoped_ptr<ResolveProxyMsgHelper> helper( - new ResolveProxyMsgHelper(&delegate, &service)); + RunnerBridge runner; GURL url1("http://www.google1.com/"); GURL url2("http://www.google2.com/"); @@ -198,32 +343,35 @@ TEST(ResolveProxyMsgHelperTest, CancelPendingRequests) { IPC::Message* msg2 = new IPC::Message(); IPC::Message* msg3 = new IPC::Message(); - // Start three requests. Since the proxy resolver is async, all the - // requests will be pending. + // Make the next request block. + runner.proxy_resolver()->x->Block(); - helper->Start(url1, msg1); + // Start three requests; since the first one blocked, the other two should + // be pending. - // Finish ProxyService's initialization. - resolver->pending_set_pac_script_request()->CompleteNow(net::OK); + scoped_ptr<ResultFuture> result1(runner.Start(url1, msg1)); + scoped_ptr<ResultFuture> result2(runner.Start(url2, msg2)); + scoped_ptr<ResultFuture> result3(runner.Start(url3, msg3)); - helper->Start(url2, msg2); - helper->Start(url3, msg3); - - // ResolveProxyHelper only keeps 1 request outstanding in ProxyService - // at a time. - ASSERT_EQ(1u, resolver->pending_requests().size()); - EXPECT_EQ(url1, resolver->pending_requests()[0]->url()); + result3->WaitUntilStarted(); // Delete the underlying ResolveProxyMsgHelper -- this should cancel all // the requests which are outstanding. - helper.reset(); - - // The pending requests sent to the proxy resolver should have been cancelled. + runner.DestroyAsyncRunner(); - EXPECT_EQ(0u, resolver->pending_requests().size()); + // Unblocking the proxy resolver means the three requests can complete -- + // however they should not try to notify the delegate since we have already + // deleted the helper. + runner.proxy_resolver()->x->Unblock(); - EXPECT_EQ(NULL, delegate.pending_result()); + // Check that none of the requests were sent to the delegate. + EXPECT_FALSE( + result1->TimedWaitUntilDone(base::TimeDelta::FromMilliseconds(2))); + EXPECT_FALSE( + result2->TimedWaitUntilDone(base::TimeDelta::FromMilliseconds(2))); + EXPECT_FALSE( + result3->TimedWaitUntilDone(base::TimeDelta::FromMilliseconds(2))); // It should also be the case that msg1, msg2, msg3 were deleted by the - // cancellation. (Else will show up as a leak in Purify/Valgrind). + // cancellation. (Else will show up as a leak in Purify). } diff --git a/net/net.gyp b/net/net.gyp index cc993c7..3bba793 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -498,7 +498,6 @@ 'http/http_util_unittest.cc', 'http/http_vary_data_unittest.cc', 'proxy/init_proxy_resolver_unittest.cc', - 'proxy/mock_proxy_resolver.h', 'proxy/proxy_config_service_linux_unittest.cc', 'proxy/proxy_config_service_win_unittest.cc', 'proxy/proxy_config_unittest.cc', diff --git a/net/proxy/mock_proxy_resolver.h b/net/proxy/mock_proxy_resolver.h index 0b1e8a5..0dac40b 100644 --- a/net/proxy/mock_proxy_resolver.h +++ b/net/proxy/mock_proxy_resolver.h @@ -1,18 +1,40 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2006-2008 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. -#ifndef NET_PROXY_MOCK_PROXY_RESOLVER_H_ -#define NET_PROXY_MOCK_PROXY_RESOLVER_H_ - #include <vector> #include "base/logging.h" +#include "base/string_util.h" #include "googleurl/src/gurl.h" #include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/proxy/proxy_config_service.h" #include "net/proxy/proxy_resolver.h" +#include "net/proxy/proxy_script_fetcher.h" +#include "net/proxy/proxy_service.h" +#include "testing/gtest/include/gtest/gtest.h" +// TODO(eroman): Write a test which exercises +// ProxyService::SuspendAllPendingRequests(). namespace net { +namespace { + +class MockProxyConfigService: public ProxyConfigService { + public: + MockProxyConfigService() {} // Direct connect. + explicit MockProxyConfigService(const ProxyConfig& pc) : config(pc) {} + explicit MockProxyConfigService(const std::string& pac_url) { + config.pac_url = GURL(pac_url); + } + + virtual int GetProxyConfig(ProxyConfig* results) { + *results = config; + return OK; + } + + ProxyConfig config; +}; // Asynchronous mock proxy resolver. All requests complete asynchronously, // user must call Request::CompleteNow() on a pending request to signal it. @@ -111,7 +133,7 @@ class MockAsyncProxyResolverBase : public ProxyResolver { virtual int SetPacScript(const GURL& pac_url, const std::string& pac_bytes, CompletionCallback* callback) { - DCHECK(!pending_set_pac_script_request_.get()); + EXPECT_EQ(NULL, pending_set_pac_script_request_.get()); pending_set_pac_script_request_.reset( new SetPacScriptRequest(this, pac_url, pac_bytes, callback)); // Finished when user calls SetPacScriptRequest::CompleteNow(). @@ -138,7 +160,7 @@ class MockAsyncProxyResolverBase : public ProxyResolver { } void RemovePendingSetPacScriptRequest(SetPacScriptRequest* request) { - DCHECK_EQ(request, pending_set_pac_script_request()); + EXPECT_EQ(request, pending_set_pac_script_request()); pending_set_pac_script_request_.reset(); } @@ -164,6 +186,1343 @@ class MockAsyncProxyResolverExpectsBytes : public MockAsyncProxyResolverBase { : MockAsyncProxyResolverBase(true /*expects_pac_bytes*/) {} }; -} // namespace net +} // namespace + +// A mock ProxyScriptFetcher. No result will be returned to the fetch client +// until we call NotifyFetchCompletion() to set the results. +class MockProxyScriptFetcher : public ProxyScriptFetcher { + public: + MockProxyScriptFetcher() + : pending_request_callback_(NULL), pending_request_bytes_(NULL) { + } + + // ProxyScriptFetcher implementation. + virtual int Fetch(const GURL& url, + std::string* bytes, + CompletionCallback* callback) { + DCHECK(!has_pending_request()); + + // Save the caller's information, and have them wait. + pending_request_url_ = url; + pending_request_callback_ = callback; + pending_request_bytes_ = bytes; + return ERR_IO_PENDING; + } + + void NotifyFetchCompletion(int result, const std::string& bytes) { + DCHECK(has_pending_request()); + *pending_request_bytes_ = bytes; + CompletionCallback* callback = pending_request_callback_; + pending_request_callback_ = NULL; + callback->Run(result); + } + + virtual void Cancel() {} + + const GURL& pending_request_url() const { + return pending_request_url_; + } + + bool has_pending_request() const { + return pending_request_callback_ != NULL; + } + + private: + GURL pending_request_url_; + CompletionCallback* pending_request_callback_; + std::string* pending_request_bytes_; +}; + +TEST(ProxyServiceTest, Direct) { + MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver; + ProxyService service(new MockProxyConfigService, resolver); + + GURL url("http://www.google.com/"); + + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(resolver->pending_requests().empty()); + + EXPECT_TRUE(info.is_direct()); +} + +TEST(ProxyServiceTest, PAC) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver; + + ProxyService service(config_service, resolver); + + GURL url("http://www.google.com/"); + + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(url, &info, &callback, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(url, resolver->pending_requests()[0]->url()); + + // Set the result in proxy resolver. + resolver->pending_requests()[0]->results()->UseNamedProxy("foopy"); + resolver->pending_requests()[0]->CompleteNow(OK); + + EXPECT_EQ(OK, callback.WaitForResult()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy:80", info.proxy_server().ToURI()); +} + +// Test that the proxy resolver does not see the URL's username/password +// or its reference section. +TEST(ProxyServiceTest, PAC_NoIdentityOrHash) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver; + + ProxyService service(config_service, resolver); + + GURL url("http://username:password@www.google.com/?ref#hash#hash"); + + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(url, &info, &callback, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + // The URL should have been simplified, stripping the username/password/hash. + EXPECT_EQ(GURL("http://www.google.com/?ref"), + resolver->pending_requests()[0]->url()); + + // We end here without ever completing the request -- destruction of + // ProxyService will cancel the outstanding request. +} + +TEST(ProxyServiceTest, PAC_FailoverToDirect) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver; + + ProxyService service(config_service, resolver); + + GURL url("http://www.google.com/"); + + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, &info, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(url, resolver->pending_requests()[0]->url()); + + // Set the result in proxy resolver. + resolver->pending_requests()[0]->results()->UseNamedProxy("foopy:8080"); + resolver->pending_requests()[0]->CompleteNow(OK); + + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy:8080", info.proxy_server().ToURI()); + + // Now, imagine that connecting to foopy:8080 fails. + TestCompletionCallback callback2; + rv = service.ReconsiderProxyAfterError(url, &info, &callback2, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info.is_direct()); +} + +TEST(ProxyServiceTest, ProxyResolverFails) { + // Test what happens when the ProxyResolver fails (this could represent + // a failure to download the PAC script in the case of ProxyResolvers which + // do the fetch internally.) + // TODO(eroman): change this comment. + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver; + + ProxyService service(config_service, resolver); + + // Start first resolve request. + GURL url("http://www.google.com/"); + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, &info, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(url, resolver->pending_requests()[0]->url()); + + // Fail the first resolve request in MockAsyncProxyResolver. + resolver->pending_requests()[0]->CompleteNow(ERR_FAILED); + + EXPECT_EQ(ERR_FAILED, callback1.WaitForResult()); + + // The second resolve request will automatically select direct connect, + // because it has cached the configuration as being bad. + TestCompletionCallback callback2; + rv = service.ResolveProxy(url, &info, &callback2, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info.is_direct()); + EXPECT_TRUE(resolver->pending_requests().empty()); + + // But, if that fails, then we should give the proxy config another shot + // since we have never tried it with this URL before. + TestCompletionCallback callback3; + rv = service.ReconsiderProxyAfterError(url, &info, &callback3, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(url, resolver->pending_requests()[0]->url()); + + // Set the result in proxy resolver. + resolver->pending_requests()[0]->results()->UseNamedProxy("foopy_valid:8080"); + resolver->pending_requests()[0]->CompleteNow(OK); + + EXPECT_EQ(OK, callback3.WaitForResult()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy_valid:8080", info.proxy_server().ToURI()); +} + +TEST(ProxyServiceTest, ProxyFallback) { + // Test what happens when we specify multiple proxy servers and some of them + // are bad. + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver; + + ProxyService service(config_service, resolver); + + GURL url("http://www.google.com/"); + + // Get the proxy information. + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, &info, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(url, resolver->pending_requests()[0]->url()); + + // Set the result in proxy resolver. + resolver->pending_requests()[0]->results()->UseNamedProxy( + "foopy1:8080;foopy2:9090"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // The first item is valid. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + + // Fake an error on the proxy. + TestCompletionCallback callback2; + rv = service.ReconsiderProxyAfterError(url, &info, &callback2, NULL); + EXPECT_EQ(OK, rv); + + // The second proxy should be specified. + EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI()); + + TestCompletionCallback callback3; + rv = service.ResolveProxy(url, &info, &callback3, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(url, resolver->pending_requests()[0]->url()); + + // Set the result in proxy resolver -- the second result is already known + // to be bad. + resolver->pending_requests()[0]->results()->UseNamedProxy( + "foopy3:7070;foopy1:8080;foopy2:9090"); + resolver->pending_requests()[0]->CompleteNow(OK); + + EXPECT_EQ(OK, callback3.WaitForResult()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy3:7070", info.proxy_server().ToURI()); + + // We fake another error. It should now try the third one. + TestCompletionCallback callback4; + rv = service.ReconsiderProxyAfterError(url, &info, &callback4, NULL); + EXPECT_EQ(OK, rv); + EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI()); + + // Fake another error, the last proxy is gone, the list should now be empty. + TestCompletionCallback callback5; + rv = service.ReconsiderProxyAfterError(url, &info, &callback5, NULL); + EXPECT_EQ(OK, rv); // We try direct. + EXPECT_TRUE(info.is_direct()); + + // If it fails again, we don't have anything else to try. + TestCompletionCallback callback6; + rv = service.ReconsiderProxyAfterError(url, &info, &callback6, NULL); + EXPECT_EQ(ERR_FAILED, rv); + + // TODO(nsylvain): Test that the proxy can be retried after the delay. +} + +TEST(ProxyServiceTest, ProxyFallback_NewSettings) { + // Test proxy failover when new settings are available. + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver; + + ProxyService service(config_service, resolver); + + GURL url("http://www.google.com/"); + + // Get the proxy information. + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, &info, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(url, resolver->pending_requests()[0]->url()); + + // Set the result in proxy resolver. + resolver->pending_requests()[0]->results()->UseNamedProxy( + "foopy1:8080;foopy2:9090"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // The first item is valid. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + + // Fake an error on the proxy, and also a new configuration on the proxy. + config_service->config = ProxyConfig(); + config_service->config.pac_url = GURL("http://foopy-new/proxy.pac"); + + TestCompletionCallback callback2; + rv = service.ReconsiderProxyAfterError(url, &info, &callback2, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(GURL("http://foopy-new/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(url, resolver->pending_requests()[0]->url()); + + resolver->pending_requests()[0]->results()->UseNamedProxy( + "foopy1:8080;foopy2:9090"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // The first proxy is still there since the configuration changed. + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + + // We fake another error. It should now ignore the first one. + TestCompletionCallback callback3; + rv = service.ReconsiderProxyAfterError(url, &info, &callback3, NULL); + EXPECT_EQ(OK, rv); + EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI()); + + // We simulate a new configuration. + config_service->config = ProxyConfig(); + config_service->config.pac_url = GURL("http://foopy-new2/proxy.pac"); + + // We fake another error. It should go back to the first proxy. + TestCompletionCallback callback4; + rv = service.ReconsiderProxyAfterError(url, &info, &callback4, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(GURL("http://foopy-new2/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(url, resolver->pending_requests()[0]->url()); + + resolver->pending_requests()[0]->results()->UseNamedProxy( + "foopy1:8080;foopy2:9090"); + resolver->pending_requests()[0]->CompleteNow(OK); + + EXPECT_EQ(OK, callback4.WaitForResult()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); +} + +TEST(ProxyServiceTest, ProxyFallback_BadConfig) { + // Test proxy failover when the configuration is bad. + + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver; + + ProxyService service(config_service, resolver); + + GURL url("http://www.google.com/"); + + // Get the proxy information. + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy(url, &info, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(url, resolver->pending_requests()[0]->url()); + + resolver->pending_requests()[0]->results()->UseNamedProxy( + "foopy1:8080;foopy2:9090"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // The first item is valid. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + + // Fake a proxy error. + TestCompletionCallback callback2; + rv = service.ReconsiderProxyAfterError(url, &info, &callback2, NULL); + EXPECT_EQ(OK, rv); + + // The first proxy is ignored, and the second one is selected. + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI()); + + // Fake a PAC failure. + ProxyInfo info2; + TestCompletionCallback callback3; + rv = service.ResolveProxy(url, &info2, &callback3, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(url, resolver->pending_requests()[0]->url()); + + resolver->pending_requests()[0]->CompleteNow(ERR_FAILED); + + // No proxy servers are returned. It's a direct connection. + EXPECT_EQ(ERR_FAILED, callback3.WaitForResult()); + EXPECT_TRUE(info2.is_direct()); + + // The PAC will now be fixed and will return a proxy server. + // It should also clear the list of bad proxies. + + // Try to resolve, it will still return "direct" because we have no reason + // to check the config since everything works. + ProxyInfo info3; + TestCompletionCallback callback4; + rv = service.ResolveProxy(url, &info3, &callback4, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info3.is_direct()); + + // But if the direct connection fails, we check if the ProxyInfo tried to + // resolve the proxy before, and if not (like in this case), we give the + // PAC another try. + TestCompletionCallback callback5; + rv = service.ReconsiderProxyAfterError(url, &info3, &callback5, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(url, resolver->pending_requests()[0]->url()); + + resolver->pending_requests()[0]->results()->UseNamedProxy( + "foopy1:8080;foopy2:9090"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // The first proxy is still there since the list of bad proxies got cleared. + EXPECT_EQ(OK, callback5.WaitForResult()); + EXPECT_FALSE(info3.is_direct()); + EXPECT_EQ("foopy1:8080", info3.proxy_server().ToURI()); +} + +TEST(ProxyServiceTest, ProxyBypassList) { + // Test what happens when a proxy bypass list is specified. + + ProxyInfo info; + ProxyConfig config; + config.proxy_rules.ParseFromString("foopy1:8080;foopy2:9090"); + config.auto_detect = false; + config.proxy_bypass_local_names = true; + + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver()); + GURL url("http://www.google.com/"); + // Get the proxy information. + TestCompletionCallback callback; + int rv = service.ResolveProxy(url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(info.is_direct()); + } + + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver()); + GURL test_url("http://local"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info.is_direct()); + } + + config.proxy_bypass.clear(); + config.proxy_bypass.push_back("*.org"); + config.proxy_bypass_local_names = true; + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("http://www.webkit.org"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info.is_direct()); + } + + config.proxy_bypass.clear(); + config.proxy_bypass.push_back("*.org"); + config.proxy_bypass.push_back("7*"); + config.proxy_bypass_local_names = true; + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("http://74.125.19.147"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info.is_direct()); + } + + config.proxy_bypass.clear(); + config.proxy_bypass.push_back("*.org"); + config.proxy_bypass_local_names = true; + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("http://www.msn.com"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(info.is_direct()); + } + + config.proxy_bypass.clear(); + config.proxy_bypass.push_back("*.MSN.COM"); + config.proxy_bypass_local_names = true; + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("http://www.msnbc.msn.com"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info.is_direct()); + } + + config.proxy_bypass.clear(); + config.proxy_bypass.push_back("*.msn.com"); + config.proxy_bypass_local_names = true; + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("HTTP://WWW.MSNBC.MSN.COM"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info.is_direct()); + } +} + +TEST(ProxyServiceTest, ProxyBypassListWithPorts) { + // Test port specification in bypass list entries. + ProxyInfo info; + ProxyConfig config; + config.proxy_rules.ParseFromString("foopy1:8080;foopy2:9090"); + config.auto_detect = false; + config.proxy_bypass_local_names = false; + + config.proxy_bypass.clear(); + config.proxy_bypass.push_back("*.example.com:99"); + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + { + GURL test_url("http://www.example.com:99"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info.is_direct()); + } + { + GURL test_url("http://www.example.com:100"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(info.is_direct()); + } + { + GURL test_url("http://www.example.com"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(info.is_direct()); + } + } + + config.proxy_bypass.clear(); + config.proxy_bypass.push_back("*.example.com:80"); + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("http://www.example.com"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info.is_direct()); + } + + config.proxy_bypass.clear(); + config.proxy_bypass.push_back("*.example.com"); + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("http://www.example.com:99"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info.is_direct()); + } + + // IPv6 with port. + config.proxy_bypass.clear(); + config.proxy_bypass.push_back("[3ffe:2a00:100:7031::1]:99"); + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + { + GURL test_url("http://[3ffe:2a00:100:7031::1]:99/"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info.is_direct()); + } + { + GURL test_url("http://[3ffe:2a00:100:7031::1]/"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(info.is_direct()); + } + } + + // IPv6 without port. The bypass entry ought to work without the + // brackets, but the bypass matching logic in ProxyService is + // currently limited. + config.proxy_bypass.clear(); + config.proxy_bypass.push_back("[3ffe:2a00:100:7031::1]"); + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + { + GURL test_url("http://[3ffe:2a00:100:7031::1]:99/"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info.is_direct()); + } + { + GURL test_url("http://[3ffe:2a00:100:7031::1]/"); + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info.is_direct()); + } + } +} + +TEST(ProxyServiceTest, PerProtocolProxyTests) { + ProxyConfig config; + config.proxy_rules.ParseFromString("http=foopy1:8080;https=foopy2:8080"); + config.auto_detect = false; + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("http://www.msn.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + } + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("ftp://ftp.google.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(info.is_direct()); + EXPECT_EQ("direct://", info.proxy_server().ToURI()); + } + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("https://webbranch.techcu.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy2:8080", info.proxy_server().ToURI()); + } + { + config.proxy_rules.ParseFromString("foopy1:8080"); + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("http://www.microsoft.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + } +} + +// If only HTTP and a SOCKS proxy are specified, check if ftp/https queries +// fall back to the SOCKS proxy. +TEST(ProxyServiceTest, DefaultProxyFallbackToSOCKS) { + ProxyConfig config; + config.proxy_rules.ParseFromString("http=foopy1:8080;socks=foopy2:1080"); + config.auto_detect = false; + EXPECT_EQ(ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME, + config.proxy_rules.type); + + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("http://www.msn.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + } + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("ftp://ftp.google.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("socks4://foopy2:1080", info.proxy_server().ToURI()); + } + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("https://webbranch.techcu.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("socks4://foopy2:1080", info.proxy_server().ToURI()); + } + { + ProxyService service(new MockProxyConfigService(config), + new MockAsyncProxyResolver); + GURL test_url("unknown://www.microsoft.com"); + ProxyInfo info; + TestCompletionCallback callback; + int rv = service.ResolveProxy(test_url, &info, &callback, NULL); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ("socks4://foopy2:1080", info.proxy_server().ToURI()); + } +} + +// Test cancellation of an in-progress request. +TEST(ProxyServiceTest, CancelInProgressRequest) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver; + + ProxyService service(config_service, resolver); + + // Start 3 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://request1"), &info1, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Nothing has been sent to the proxy resolver yet, since the proxy + // resolver has not been configured yet. + ASSERT_EQ(0u, resolver->pending_requests().size()); + + // Successfully initialize the PAC script. + EXPECT_EQ(GURL("http://foopy/proxy.pac"), + resolver->pending_set_pac_script_request()->pac_url()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url()); + + ProxyInfo info2; + TestCompletionCallback callback2; + ProxyService::PacRequest* request2; + rv = service.ResolveProxy( + GURL("http://request2"), &info2, &callback2, &request2); + EXPECT_EQ(ERR_IO_PENDING, rv); + ASSERT_EQ(2u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[1]->url()); + + ProxyInfo info3; + TestCompletionCallback callback3; + rv = service.ResolveProxy( + GURL("http://request3"), &info3, &callback3, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + ASSERT_EQ(3u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request3"), resolver->pending_requests()[2]->url()); + + // Cancel the second request + service.CancelPacRequest(request2); + + ASSERT_EQ(2u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url()); + EXPECT_EQ(GURL("http://request3"), resolver->pending_requests()[1]->url()); + + // Complete the two un-cancelled requests. + // We complete the last one first, just to mix it up a bit. + resolver->pending_requests()[1]->results()->UseNamedProxy("request3:80"); + resolver->pending_requests()[1]->CompleteNow(OK); + + resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // Complete and verify that requests ran as expected. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + EXPECT_FALSE(callback2.have_result()); // Cancelled. + ASSERT_EQ(1u, resolver->cancelled_requests().size()); + EXPECT_EQ(GURL("http://request2"), resolver->cancelled_requests()[0]->url()); + + EXPECT_EQ(OK, callback3.WaitForResult()); + EXPECT_EQ("request3:80", info3.proxy_server().ToURI()); +} + +// Test the initial PAC download for ProxyResolverWithoutFetch. +TEST(ProxyServiceTest, InitialPACScriptDownload) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolverExpectsBytes* resolver = + new MockAsyncProxyResolverExpectsBytes; + + ProxyService service(config_service, resolver); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetcher(fetcher); + + // Start 3 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://request1"), &info1, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // The first request should have triggered download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy( + GURL("http://request2"), &info2, &callback2, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + ProxyInfo info3; + TestCompletionCallback callback3; + rv = service.ResolveProxy( + GURL("http://request3"), &info3, &callback3, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Nothing has been sent to the resolver yet. + EXPECT_TRUE(resolver->pending_requests().empty()); + + // At this point the ProxyService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + fetcher->NotifyFetchCompletion(OK, "pac-v1"); + + // Now that the PAC script is downloaded, it will have been sent to the proxy + // resolver. + EXPECT_EQ("pac-v1", resolver->pending_set_pac_script_request()->pac_bytes()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(3u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url()); + EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[1]->url()); + EXPECT_EQ(GURL("http://request3"), resolver->pending_requests()[2]->url()); + + // Complete all the requests (in some order). + // Note that as we complete requests, they shift up in |pending_requests()|. + + resolver->pending_requests()[2]->results()->UseNamedProxy("request3:80"); + resolver->pending_requests()[2]->CompleteNow(OK); + + resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + resolver->pending_requests()[0]->results()->UseNamedProxy("request2:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // Complete and verify that requests ran as expected. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); + + EXPECT_EQ(OK, callback3.WaitForResult()); + EXPECT_EQ("request3:80", info3.proxy_server().ToURI()); +} + +// Test cancellation of a request, while the PAC script is being fetched. +TEST(ProxyServiceTest, CancelWhilePACFetching) { + MockProxyConfigService* config_service = + new MockProxyConfigService("http://foopy/proxy.pac"); + + MockAsyncProxyResolverExpectsBytes* resolver = + new MockAsyncProxyResolverExpectsBytes; + + ProxyService service(config_service, resolver); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetcher(fetcher); + + // Start 3 requests. + ProxyInfo info1; + TestCompletionCallback callback1; + ProxyService::PacRequest* request1; + int rv = service.ResolveProxy( + GURL("http://request1"), &info1, &callback1, &request1); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // The first request should have triggered download of PAC script. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + + ProxyInfo info2; + TestCompletionCallback callback2; + ProxyService::PacRequest* request2; + rv = service.ResolveProxy( + GURL("http://request2"), &info2, &callback2, &request2); + EXPECT_EQ(ERR_IO_PENDING, rv); + + ProxyInfo info3; + TestCompletionCallback callback3; + rv = service.ResolveProxy( + GURL("http://request3"), &info3, &callback3, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Nothing has been sent to the resolver yet. + EXPECT_TRUE(resolver->pending_requests().empty()); + + // Cancel the first 2 requests. + service.CancelPacRequest(request1); + service.CancelPacRequest(request2); + + // At this point the ProxyService should be waiting for the + // ProxyScriptFetcher to invoke its completion callback, notifying it of + // PAC script download completion. + fetcher->NotifyFetchCompletion(OK, "pac-v1"); + + // Now that the PAC script is downloaded, it will have been sent to the + // proxy resolver. + EXPECT_EQ("pac-v1", resolver->pending_set_pac_script_request()->pac_bytes()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); -#endif // NET_PROXY_MOCK_PROXY_RESOLVER_H_ + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request3"), resolver->pending_requests()[0]->url()); + + // Complete all the requests. + resolver->pending_requests()[0]->results()->UseNamedProxy("request3:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + EXPECT_EQ(OK, callback3.WaitForResult()); + EXPECT_EQ("request3:80", info3.proxy_server().ToURI()); + + EXPECT_TRUE(resolver->cancelled_requests().empty()); + + EXPECT_FALSE(callback1.have_result()); // Cancelled. + EXPECT_FALSE(callback2.have_result()); // Cancelled. +} + +// Test that if auto-detect fails, we fall-back to the custom pac. +TEST(ProxyServiceTest, FallbackFromAutodetectToCustomPac) { + ProxyConfig config; + config.auto_detect = true; + config.pac_url = GURL("http://foopy/proxy.pac"); + config.proxy_rules.ParseFromString("http=foopy:80"); // Won't be used. + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolverExpectsBytes* resolver = + new MockAsyncProxyResolverExpectsBytes; + ProxyService service(config_service, resolver); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetcher(fetcher); + + // Start 2 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://request1"), &info1, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + ProxyInfo info2; + TestCompletionCallback callback2; + ProxyService::PacRequest* request2; + rv = service.ResolveProxy( + GURL("http://request2"), &info2, &callback2, &request2); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Check that nothing has been sent to the proxy resolver yet. + ASSERT_EQ(0u, resolver->pending_requests().size()); + + // It should be trying to auto-detect first -- FAIL the autodetect during + // the script download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(ERR_FAILED, ""); + + // Next it should be trying the custom PAC url. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, "custom-pac-script"); + + EXPECT_EQ("custom-pac-script", + resolver->pending_set_pac_script_request()->pac_bytes()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + // Now finally, the pending requests should have been sent to the resolver + // (which was initialized with custom PAC script). + + ASSERT_EQ(2u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url()); + EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[1]->url()); + + // Complete the pending requests. + resolver->pending_requests()[1]->results()->UseNamedProxy("request2:80"); + resolver->pending_requests()[1]->CompleteNow(OK); + resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // Verify that requests ran as expected. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); +} + +// This is the same test as FallbackFromAutodetectToCustomPac, except +// the auto-detect script fails parsing rather than downloading. +TEST(ProxyServiceTest, FallbackFromAutodetectToCustomPac2) { + ProxyConfig config; + config.auto_detect = true; + config.pac_url = GURL("http://foopy/proxy.pac"); + config.proxy_rules.ParseFromString("http=foopy:80"); // Won't be used. + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolverExpectsBytes* resolver = + new MockAsyncProxyResolverExpectsBytes; + ProxyService service(config_service, resolver); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetcher(fetcher); + + // Start 2 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://request1"), &info1, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + ProxyInfo info2; + TestCompletionCallback callback2; + ProxyService::PacRequest* request2; + rv = service.ResolveProxy( + GURL("http://request2"), &info2, &callback2, &request2); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Check that nothing has been sent to the proxy resolver yet. + ASSERT_EQ(0u, resolver->pending_requests().size()); + + // It should be trying to auto-detect first -- succeed the download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, "invalid-script-contents"); + + // Simulate a parse error. + EXPECT_EQ("invalid-script-contents", + resolver->pending_set_pac_script_request()->pac_bytes()); + resolver->pending_set_pac_script_request()->CompleteNow( + ERR_PAC_SCRIPT_FAILED); + + // Next it should be trying the custom PAC url. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, "custom-pac-script"); + + EXPECT_EQ("custom-pac-script", + resolver->pending_set_pac_script_request()->pac_bytes()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + // Now finally, the pending requests should have been sent to the resolver + // (which was initialized with custom PAC script). + + ASSERT_EQ(2u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url()); + EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[1]->url()); + + // Complete the pending requests. + resolver->pending_requests()[1]->results()->UseNamedProxy("request2:80"); + resolver->pending_requests()[1]->CompleteNow(OK); + resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // Verify that requests ran as expected. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); +} + +// Test that if all of auto-detect, a custom PAC script, and manual settings +// are given, then we will try them in that order. +TEST(ProxyServiceTest, FallbackFromAutodetectToCustomToManual) { + ProxyConfig config; + config.auto_detect = true; + config.pac_url = GURL("http://foopy/proxy.pac"); + config.proxy_rules.ParseFromString("http=foopy:80"); + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolverExpectsBytes* resolver = + new MockAsyncProxyResolverExpectsBytes; + ProxyService service(config_service, resolver); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetcher(fetcher); + + // Start 2 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://request1"), &info1, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + ProxyInfo info2; + TestCompletionCallback callback2; + ProxyService::PacRequest* request2; + rv = service.ResolveProxy( + GURL("http://request2"), &info2, &callback2, &request2); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Check that nothing has been sent to the proxy resolver yet. + ASSERT_EQ(0u, resolver->pending_requests().size()); + + // It should be trying to auto-detect first -- fail the download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(ERR_FAILED, ""); + + // Next it should be trying the custom PAC url -- fail the download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(ERR_FAILED, ""); + + // Since we never managed to initialize a ProxyResolver, nothing should have + // been sent to it. + ASSERT_EQ(0u, resolver->pending_requests().size()); + + // Verify that requests ran as expected -- they should have fallen back to + // the manual proxy configuration for HTTP urls. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_EQ("foopy:80", info1.proxy_server().ToURI()); + + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ("foopy:80", info2.proxy_server().ToURI()); +} + +// Test that the bypass rules are NOT applied when using autodetect. +TEST(ProxyServiceTest, BypassDoesntApplyToPac) { + ProxyConfig config; + config.auto_detect = true; + config.pac_url = GURL("http://foopy/proxy.pac"); + config.proxy_rules.ParseFromString("http=foopy:80"); // Not used. + config.proxy_bypass.push_back("www.google.com"); + + MockProxyConfigService* config_service = new MockProxyConfigService(config); + MockAsyncProxyResolverExpectsBytes* resolver = + new MockAsyncProxyResolverExpectsBytes; + ProxyService service(config_service, resolver); + + MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher; + service.SetProxyScriptFetcher(fetcher); + + // Start 1 requests. + + ProxyInfo info1; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://www.google.com"), &info1, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // Check that nothing has been sent to the proxy resolver yet. + ASSERT_EQ(0u, resolver->pending_requests().size()); + + // It should be trying to auto-detect first -- succeed the download. + EXPECT_TRUE(fetcher->has_pending_request()); + EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url()); + fetcher->NotifyFetchCompletion(OK, "auto-detect"); + + EXPECT_EQ("auto-detect", + resolver->pending_set_pac_script_request()->pac_bytes()); + resolver->pending_set_pac_script_request()->CompleteNow(OK); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://www.google.com"), + resolver->pending_requests()[0]->url()); + + // Complete the pending request. + resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + // Verify that request ran as expected. + EXPECT_EQ(OK, callback1.WaitForResult()); + EXPECT_EQ("request1:80", info1.proxy_server().ToURI()); + + // Start another request, it should pickup the bypass item. + ProxyInfo info2; + TestCompletionCallback callback2; + rv = service.ResolveProxy( + GURL("http://www.google.com"), &info2, &callback2, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + ASSERT_EQ(1u, resolver->pending_requests().size()); + EXPECT_EQ(GURL("http://www.google.com"), + resolver->pending_requests()[0]->url()); + + // Complete the pending request. + resolver->pending_requests()[0]->results()->UseNamedProxy("request2:80"); + resolver->pending_requests()[0]->CompleteNow(OK); + + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ("request2:80", info2.proxy_server().ToURI()); +} + +TEST(ProxyServiceTest, ResetProxyConfigService) { + ProxyConfig config1; + config1.proxy_rules.ParseFromString("foopy1:8080"); + config1.auto_detect = false; + ProxyService service(new MockProxyConfigService(config1), + new MockAsyncProxyResolverExpectsBytes); + + ProxyInfo info; + TestCompletionCallback callback1; + int rv = service.ResolveProxy( + GURL("http://request1"), &info, &callback1, NULL); + EXPECT_EQ(OK, rv); + EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI()); + + ProxyConfig config2; + config2.proxy_rules.ParseFromString("foopy2:8080"); + config2.auto_detect = false; + service.ResetConfigService(new MockProxyConfigService(config2)); + TestCompletionCallback callback2; + rv = service.ResolveProxy(GURL("http://request2"), &info, &callback2, NULL); + EXPECT_EQ(OK, rv); + EXPECT_EQ("foopy2:8080", info.proxy_server().ToURI()); +} + +TEST(ProxyServiceTest, IsLocalName) { + const struct { + const char* url; + bool expected_is_local; + } tests[] = { + // Single-component hostnames are considered local. + {"http://localhost/x", true}, + {"http://www", true}, + + // IPv4 loopback interface. + {"http://127.0.0.1/x", true}, + {"http://127.0.0.1:80/x", true}, + + // IPv6 loopback interface. + {"http://[::1]:80/x", true}, + {"http://[0:0::1]:6233/x", true}, + {"http://[0:0:0:0:0:0:0:1]/x", true}, + + // Non-local URLs. + {"http://foo.com/", false}, + {"http://localhost.i/", false}, + {"http://www.google.com/", false}, + {"http://192.168.0.1/", false}, + + // Try with different protocols. + {"ftp://127.0.0.1/x", true}, + {"ftp://foobar.com/x", false}, + + // This is a bit of a gray-area, but GURL does not strip trailing dots + // in host-names, so the following are considered non-local. + {"http://www./x", false}, + {"http://localhost./x", false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { + SCOPED_TRACE(StringPrintf("Test[%d]: %s", i, tests[i].url)); + bool is_local = ProxyService::IsLocalName(GURL(tests[i].url)); + EXPECT_EQ(tests[i].expected_is_local, is_local); + } +} + +} // namespace net diff --git a/net/proxy/proxy_service_unittest.cc b/net/proxy/proxy_service_unittest.cc index f4deb67..3b3eb49 100644 --- a/net/proxy/proxy_service_unittest.cc +++ b/net/proxy/proxy_service_unittest.cc @@ -9,7 +9,6 @@ #include "googleurl/src/gurl.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" -#include "net/proxy/mock_proxy_resolver.h" #include "net/proxy/proxy_config_service.h" #include "net/proxy/proxy_resolver.h" #include "net/proxy/proxy_script_fetcher.h" @@ -37,6 +36,156 @@ class MockProxyConfigService: public ProxyConfigService { ProxyConfig config; }; +// Asynchronous mock proxy resolver. All requests complete asynchronously, +// user must call Request::CompleteNow() on a pending request to signal it. +class MockAsyncProxyResolverBase : public ProxyResolver { + public: + class Request : public base::RefCounted<Request> { + public: + Request(MockAsyncProxyResolverBase* resolver, + const GURL& url, + ProxyInfo* results, + CompletionCallback* callback) + : resolver_(resolver), + url_(url), + results_(results), + callback_(callback), + origin_loop_(MessageLoop::current()) { + } + + const GURL& url() const { return url_; } + ProxyInfo* results() const { return results_; } + CompletionCallback* callback() const { return callback_; } + + void CompleteNow(int rv) { + CompletionCallback* callback = callback_; + + // May delete |this|. + resolver_->RemovePendingRequest(this); + + callback->Run(rv); + } + + private: + MockAsyncProxyResolverBase* resolver_; + const GURL url_; + ProxyInfo* results_; + CompletionCallback* callback_; + MessageLoop* origin_loop_; + }; + + class SetPacScriptRequest { + public: + SetPacScriptRequest(MockAsyncProxyResolverBase* resolver, + const GURL& pac_url, + const std::string& pac_bytes, + CompletionCallback* callback) + : resolver_(resolver), + pac_url_(pac_url), + pac_bytes_(pac_bytes), + callback_(callback), + origin_loop_(MessageLoop::current()) { + } + + const GURL& pac_url() const { return pac_url_; } + const std::string& pac_bytes() const { return pac_bytes_; } + + void CompleteNow(int rv) { + CompletionCallback* callback = callback_; + + // Will delete |this|. + resolver_->RemovePendingSetPacScriptRequest(this); + + callback->Run(rv); + } + + private: + MockAsyncProxyResolverBase* resolver_; + const GURL pac_url_; + const std::string pac_bytes_; + CompletionCallback* callback_; + MessageLoop* origin_loop_; + }; + + typedef std::vector<scoped_refptr<Request> > RequestsList; + + // ProxyResolver implementation: + virtual int GetProxyForURL(const GURL& url, + ProxyInfo* results, + CompletionCallback* callback, + RequestHandle* request_handle) { + scoped_refptr<Request> request = new Request(this, url, results, callback); + pending_requests_.push_back(request); + + if (request_handle) + *request_handle = reinterpret_cast<RequestHandle>(request.get()); + + // Test code completes the request by calling request->CompleteNow(). + return ERR_IO_PENDING; + } + + virtual void CancelRequest(RequestHandle request_handle) { + scoped_refptr<Request> request = reinterpret_cast<Request*>(request_handle); + cancelled_requests_.push_back(request); + RemovePendingRequest(request); + } + + virtual int SetPacScript(const GURL& pac_url, + const std::string& pac_bytes, + CompletionCallback* callback) { + EXPECT_EQ(NULL, pending_set_pac_script_request_.get()); + pending_set_pac_script_request_.reset( + new SetPacScriptRequest(this, pac_url, pac_bytes, callback)); + // Finished when user calls SetPacScriptRequest::CompleteNow(). + return ERR_IO_PENDING; + } + + const RequestsList& pending_requests() const { + return pending_requests_; + } + + const RequestsList& cancelled_requests() const { + return cancelled_requests_; + } + + SetPacScriptRequest* pending_set_pac_script_request() const { + return pending_set_pac_script_request_.get(); + } + + void RemovePendingRequest(Request* request) { + RequestsList::iterator it = std::find( + pending_requests_.begin(), pending_requests_.end(), request); + DCHECK(it != pending_requests_.end()); + pending_requests_.erase(it); + } + + void RemovePendingSetPacScriptRequest(SetPacScriptRequest* request) { + EXPECT_EQ(request, pending_set_pac_script_request()); + pending_set_pac_script_request_.reset(); + } + + protected: + explicit MockAsyncProxyResolverBase(bool expects_pac_bytes) + : ProxyResolver(expects_pac_bytes) {} + + private: + RequestsList pending_requests_; + RequestsList cancelled_requests_; + scoped_ptr<SetPacScriptRequest> pending_set_pac_script_request_; +}; + +class MockAsyncProxyResolver : public MockAsyncProxyResolverBase { + public: + MockAsyncProxyResolver() + : MockAsyncProxyResolverBase(false /*expects_pac_bytes*/) {} +}; + +class MockAsyncProxyResolverExpectsBytes : public MockAsyncProxyResolverBase { + public: + MockAsyncProxyResolverExpectsBytes() + : MockAsyncProxyResolverBase(true /*expects_pac_bytes*/) {} +}; + } // namespace // A mock ProxyScriptFetcher. No result will be returned to the fetch client |