| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/base/host_resolver_impl.h" |
| |
| #include <string> |
| |
| #include "base/compiler_specific.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/message_loop.h" |
| #include "base/string_util.h" |
| #include "base/stringprintf.h" |
| #include "net/base/address_list.h" |
| #include "net/base/completion_callback.h" |
| #include "net/base/mock_host_resolver.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/net_log_unittest.h" |
| #include "net/base/net_util.h" |
| #include "net/base/sys_addrinfo.h" |
| #include "net/base/test_completion_callback.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| // TODO(eroman): |
| // - Test mixing async with sync (in particular how does sync update the |
| // cache while an async is already pending). |
| |
| namespace net { |
| |
| namespace { |
| |
| HostCache* CreateDefaultCache() { |
| return new HostCache( |
| 100, // max cache entries. |
| base::TimeDelta::FromMinutes(1), |
| base::TimeDelta::FromSeconds(0)); |
| } |
| |
| static const size_t kMaxJobs = 10u; |
| |
| HostResolverImpl* CreateHostResolverImpl(HostResolverProc* resolver_proc) { |
| return new HostResolverImpl(resolver_proc, CreateDefaultCache(), kMaxJobs, |
| NULL); |
| } |
| |
| // Helper to create a HostResolver::RequestInfo. |
| HostResolver::RequestInfo CreateResolverRequest( |
| const std::string& hostname, |
| RequestPriority priority) { |
| HostResolver::RequestInfo info(HostPortPair(hostname, 80)); |
| info.set_priority(priority); |
| return info; |
| } |
| |
| // Helper to create a HostResolver::RequestInfo. |
| HostResolver::RequestInfo CreateResolverRequestForAddressFamily( |
| const std::string& hostname, |
| RequestPriority priority, |
| AddressFamily address_family) { |
| HostResolver::RequestInfo info(HostPortPair(hostname, 80)); |
| info.set_priority(priority); |
| info.set_address_family(address_family); |
| return info; |
| } |
| |
| // A variant of WaitingHostResolverProc that pushes each host mapped into a |
| // list. |
| // (and uses a manual-reset event rather than auto-reset). |
| class CapturingHostResolverProc : public HostResolverProc { |
| public: |
| struct CaptureEntry { |
| CaptureEntry(const std::string& hostname, AddressFamily address_family) |
| : hostname(hostname), address_family(address_family) {} |
| std::string hostname; |
| AddressFamily address_family; |
| }; |
| |
| typedef std::vector<CaptureEntry> CaptureList; |
| |
| explicit CapturingHostResolverProc(HostResolverProc* previous) |
| : HostResolverProc(previous), event_(true, false) { |
| } |
| |
| void Signal() { |
| event_.Signal(); |
| } |
| |
| virtual int Resolve(const std::string& hostname, |
| AddressFamily address_family, |
| HostResolverFlags host_resolver_flags, |
| AddressList* addrlist, |
| int* os_error) { |
| event_.Wait(); |
| { |
| base::AutoLock l(lock_); |
| capture_list_.push_back(CaptureEntry(hostname, address_family)); |
| } |
| return ResolveUsingPrevious(hostname, address_family, |
| host_resolver_flags, addrlist, os_error); |
| } |
| |
| CaptureList GetCaptureList() const { |
| CaptureList copy; |
| { |
| base::AutoLock l(lock_); |
| copy = capture_list_; |
| } |
| return copy; |
| } |
| |
| private: |
| ~CapturingHostResolverProc() {} |
| |
| CaptureList capture_list_; |
| mutable base::Lock lock_; |
| base::WaitableEvent event_; |
| }; |
| |
| // This resolver function creates an IPv4 address, whose numeral value |
| // describes a hash of the requested hostname, and the value of the requested |
| // address_family. |
| // |
| // The resolved address for (hostname, address_family) will take the form: |
| // 192.x.y.z |
| // |
| // Where: |
| // x = length of hostname |
| // y = ASCII value of hostname[0] |
| // z = value of address_family |
| // |
| class EchoingHostResolverProc : public HostResolverProc { |
| public: |
| EchoingHostResolverProc() : HostResolverProc(NULL) {} |
| |
| virtual int Resolve(const std::string& hostname, |
| AddressFamily address_family, |
| HostResolverFlags host_resolver_flags, |
| AddressList* addrlist, |
| int* os_error) { |
| // Encode the request's hostname and address_family in the output address. |
| std::string ip_literal = base::StringPrintf("192.%d.%d.%d", |
| static_cast<int>(hostname.size()), |
| static_cast<int>(hostname[0]), |
| static_cast<int>(address_family)); |
| |
| return SystemHostResolverProc(ip_literal, |
| ADDRESS_FAMILY_UNSPECIFIED, |
| host_resolver_flags, |
| addrlist, os_error); |
| } |
| }; |
| |
| // Helper that represents a single Resolve() result, used to inspect all the |
| // resolve results by forwarding them to Delegate. |
| class ResolveRequest { |
| public: |
| // Delegate interface, for notification when the ResolveRequest completes. |
| class Delegate { |
| public: |
| virtual ~Delegate() {} |
| virtual void OnCompleted(ResolveRequest* resolve) = 0; |
| }; |
| |
| ResolveRequest(HostResolver* resolver, |
| const std::string& hostname, |
| int port, |
| Delegate* delegate) |
| : info_(HostPortPair(hostname, port)), |
| resolver_(resolver), |
| delegate_(delegate), |
| ALLOW_THIS_IN_INITIALIZER_LIST( |
| callback_(this, &ResolveRequest::OnLookupFinished)) { |
| // Start the request. |
| int err = resolver->Resolve(info_, &addrlist_, &callback_, &req_, |
| BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, err); |
| } |
| |
| ResolveRequest(HostResolver* resolver, |
| const HostResolver::RequestInfo& info, |
| Delegate* delegate) |
| : info_(info), resolver_(resolver), delegate_(delegate), |
| ALLOW_THIS_IN_INITIALIZER_LIST( |
| callback_(this, &ResolveRequest::OnLookupFinished)) { |
| // Start the request. |
| int err = resolver->Resolve(info, &addrlist_, &callback_, &req_, |
| BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, err); |
| } |
| |
| void Cancel() { |
| resolver_->CancelRequest(req_); |
| } |
| |
| const std::string& hostname() const { |
| return info_.hostname(); |
| } |
| |
| int port() const { |
| return info_.port(); |
| } |
| |
| int result() const { |
| return result_; |
| } |
| |
| const AddressList& addrlist() const { |
| return addrlist_; |
| } |
| |
| HostResolver* resolver() const { |
| return resolver_; |
| } |
| |
| private: |
| void OnLookupFinished(int result) { |
| result_ = result; |
| delegate_->OnCompleted(this); |
| } |
| |
| // The request details. |
| HostResolver::RequestInfo info_; |
| HostResolver::RequestHandle req_; |
| |
| // The result of the resolve. |
| int result_; |
| AddressList addrlist_; |
| |
| HostResolver* resolver_; |
| |
| Delegate* delegate_; |
| CompletionCallbackImpl<ResolveRequest> callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ResolveRequest); |
| }; |
| |
| class HostResolverImplTest : public testing::Test { |
| public: |
| HostResolverImplTest() |
| : callback_called_(false), |
| ALLOW_THIS_IN_INITIALIZER_LIST( |
| callback_(this, &HostResolverImplTest::OnLookupFinished)) { |
| } |
| |
| protected: |
| bool callback_called_; |
| int callback_result_; |
| CompletionCallbackImpl<HostResolverImplTest> callback_; |
| |
| private: |
| void OnLookupFinished(int result) { |
| callback_called_ = true; |
| callback_result_ = result; |
| MessageLoop::current()->Quit(); |
| } |
| }; |
| |
| TEST_F(HostResolverImplTest, SynchronousLookup) { |
| AddressList addrlist; |
| const int kPortnum = 80; |
| |
| scoped_refptr<RuleBasedHostResolverProc> resolver_proc( |
| new RuleBasedHostResolverProc(NULL)); |
| resolver_proc->AddRule("just.testing", "192.168.1.42"); |
| |
| scoped_ptr<HostResolver> host_resolver( |
| CreateHostResolverImpl(resolver_proc)); |
| |
| HostResolver::RequestInfo info(HostPortPair("just.testing", kPortnum)); |
| CapturingBoundNetLog log(CapturingNetLog::kUnbounded); |
| int err = host_resolver->Resolve(info, &addrlist, NULL, NULL, log.bound()); |
| EXPECT_EQ(OK, err); |
| |
| CapturingNetLog::EntryList entries; |
| log.GetEntries(&entries); |
| |
| EXPECT_EQ(2u, entries.size()); |
| EXPECT_TRUE(LogContainsBeginEvent( |
| entries, 0, NetLog::TYPE_HOST_RESOLVER_IMPL)); |
| EXPECT_TRUE(LogContainsEndEvent( |
| entries, 1, NetLog::TYPE_HOST_RESOLVER_IMPL)); |
| |
| const struct addrinfo* ainfo = addrlist.head(); |
| EXPECT_EQ(static_cast<addrinfo*>(NULL), ainfo->ai_next); |
| EXPECT_EQ(sizeof(struct sockaddr_in), ainfo->ai_addrlen); |
| |
| const struct sockaddr* sa = ainfo->ai_addr; |
| const struct sockaddr_in* sa_in = (const struct sockaddr_in*) sa; |
| EXPECT_TRUE(htons(kPortnum) == sa_in->sin_port); |
| EXPECT_TRUE(htonl(0xc0a8012a) == sa_in->sin_addr.s_addr); |
| } |
| |
| TEST_F(HostResolverImplTest, AsynchronousLookup) { |
| AddressList addrlist; |
| const int kPortnum = 80; |
| |
| scoped_refptr<RuleBasedHostResolverProc> resolver_proc( |
| new RuleBasedHostResolverProc(NULL)); |
| resolver_proc->AddRule("just.testing", "192.168.1.42"); |
| |
| scoped_ptr<HostResolver> host_resolver( |
| CreateHostResolverImpl(resolver_proc)); |
| |
| HostResolver::RequestInfo info(HostPortPair("just.testing", kPortnum)); |
| CapturingBoundNetLog log(CapturingNetLog::kUnbounded); |
| int err = host_resolver->Resolve(info, &addrlist, &callback_, NULL, |
| log.bound()); |
| EXPECT_EQ(ERR_IO_PENDING, err); |
| |
| CapturingNetLog::EntryList entries; |
| log.GetEntries(&entries); |
| |
| EXPECT_EQ(1u, entries.size()); |
| EXPECT_TRUE(LogContainsBeginEvent( |
| entries, 0, NetLog::TYPE_HOST_RESOLVER_IMPL)); |
| |
| MessageLoop::current()->Run(); |
| |
| ASSERT_TRUE(callback_called_); |
| ASSERT_EQ(OK, callback_result_); |
| |
| log.GetEntries(&entries); |
| |
| EXPECT_EQ(2u, entries.size()); |
| EXPECT_TRUE(LogContainsEndEvent( |
| entries, 1, NetLog::TYPE_HOST_RESOLVER_IMPL)); |
| |
| const struct addrinfo* ainfo = addrlist.head(); |
| EXPECT_EQ(static_cast<addrinfo*>(NULL), ainfo->ai_next); |
| EXPECT_EQ(sizeof(struct sockaddr_in), ainfo->ai_addrlen); |
| |
| const struct sockaddr* sa = ainfo->ai_addr; |
| const struct sockaddr_in* sa_in = (const struct sockaddr_in*) sa; |
| EXPECT_TRUE(htons(kPortnum) == sa_in->sin_port); |
| EXPECT_TRUE(htonl(0xc0a8012a) == sa_in->sin_addr.s_addr); |
| } |
| |
| TEST_F(HostResolverImplTest, CanceledAsynchronousLookup) { |
| scoped_refptr<WaitingHostResolverProc> resolver_proc( |
| new WaitingHostResolverProc(NULL)); |
| |
| CapturingNetLog net_log(CapturingNetLog::kUnbounded); |
| CapturingBoundNetLog log(CapturingNetLog::kUnbounded); |
| { |
| scoped_ptr<HostResolver> host_resolver( |
| new HostResolverImpl(resolver_proc, |
| CreateDefaultCache(), |
| kMaxJobs, |
| &net_log)); |
| AddressList addrlist; |
| const int kPortnum = 80; |
| |
| HostResolver::RequestInfo info(HostPortPair("just.testing", kPortnum)); |
| int err = host_resolver->Resolve(info, &addrlist, &callback_, NULL, |
| log.bound()); |
| EXPECT_EQ(ERR_IO_PENDING, err); |
| |
| // Make sure we will exit the queue even when callback is not called. |
| MessageLoop::current()->PostDelayedTask(FROM_HERE, |
| new MessageLoop::QuitTask(), |
| 1000); |
| MessageLoop::current()->Run(); |
| } |
| |
| resolver_proc->Signal(); |
| |
| CapturingNetLog::EntryList entries; |
| log.GetEntries(&entries); |
| |
| EXPECT_EQ(2u, entries.size()); |
| EXPECT_TRUE(LogContainsBeginEvent( |
| entries, 0, NetLog::TYPE_HOST_RESOLVER_IMPL)); |
| EXPECT_TRUE(LogContainsEndEvent( |
| entries, 1, NetLog::TYPE_HOST_RESOLVER_IMPL)); |
| |
| CapturingNetLog::EntryList net_log_entries; |
| net_log.GetEntries(&net_log_entries); |
| |
| int pos = ExpectLogContainsSomewhereAfter(net_log_entries, 0, |
| NetLog::TYPE_HOST_RESOLVER_IMPL_REQUEST, |
| NetLog::PHASE_BEGIN); |
| pos = ExpectLogContainsSomewhereAfter(net_log_entries, pos + 1, |
| NetLog::TYPE_HOST_RESOLVER_IMPL_JOB, |
| NetLog::PHASE_BEGIN); |
| // Both Job and Request need to be cancelled. |
| pos = ExpectLogContainsSomewhereAfter(net_log_entries, pos + 1, |
| NetLog::TYPE_CANCELLED, |
| NetLog::PHASE_NONE); |
| // Don't care about order in which they end, or when the other one is |
| // cancelled. |
| ExpectLogContainsSomewhereAfter(net_log_entries, pos + 1, |
| NetLog::TYPE_CANCELLED, |
| NetLog::PHASE_NONE); |
| ExpectLogContainsSomewhereAfter(net_log_entries, pos + 1, |
| NetLog::TYPE_HOST_RESOLVER_IMPL_REQUEST, |
| NetLog::PHASE_END); |
| ExpectLogContainsSomewhereAfter(net_log_entries, pos + 1, |
| NetLog::TYPE_HOST_RESOLVER_IMPL_JOB, |
| NetLog::PHASE_END); |
| |
| EXPECT_FALSE(callback_called_); |
| } |
| |
| TEST_F(HostResolverImplTest, NumericIPv4Address) { |
| // Stevens says dotted quads with AI_UNSPEC resolve to a single sockaddr_in. |
| |
| scoped_refptr<RuleBasedHostResolverProc> resolver_proc( |
| new RuleBasedHostResolverProc(NULL)); |
| resolver_proc->AllowDirectLookup("*"); |
| |
| scoped_ptr<HostResolver> host_resolver( |
| CreateHostResolverImpl(resolver_proc)); |
| AddressList addrlist; |
| const int kPortnum = 5555; |
| HostResolver::RequestInfo info(HostPortPair("127.1.2.3", kPortnum)); |
| int err = host_resolver->Resolve(info, &addrlist, NULL, NULL, BoundNetLog()); |
| EXPECT_EQ(OK, err); |
| |
| const struct addrinfo* ainfo = addrlist.head(); |
| EXPECT_EQ(static_cast<addrinfo*>(NULL), ainfo->ai_next); |
| EXPECT_EQ(sizeof(struct sockaddr_in), ainfo->ai_addrlen); |
| |
| const struct sockaddr* sa = ainfo->ai_addr; |
| const struct sockaddr_in* sa_in = (const struct sockaddr_in*) sa; |
| EXPECT_TRUE(htons(kPortnum) == sa_in->sin_port); |
| EXPECT_TRUE(htonl(0x7f010203) == sa_in->sin_addr.s_addr); |
| } |
| |
| TEST_F(HostResolverImplTest, NumericIPv6Address) { |
| scoped_refptr<RuleBasedHostResolverProc> resolver_proc( |
| new RuleBasedHostResolverProc(NULL)); |
| resolver_proc->AllowDirectLookup("*"); |
| |
| // Resolve a plain IPv6 address. Don't worry about [brackets], because |
| // the caller should have removed them. |
| scoped_ptr<HostResolver> host_resolver( |
| CreateHostResolverImpl(resolver_proc)); |
| AddressList addrlist; |
| const int kPortnum = 5555; |
| HostResolver::RequestInfo info(HostPortPair("2001:db8::1", kPortnum)); |
| int err = host_resolver->Resolve(info, &addrlist, NULL, NULL, BoundNetLog()); |
| EXPECT_EQ(OK, err); |
| |
| const struct addrinfo* ainfo = addrlist.head(); |
| EXPECT_EQ(static_cast<addrinfo*>(NULL), ainfo->ai_next); |
| EXPECT_EQ(sizeof(struct sockaddr_in6), ainfo->ai_addrlen); |
| |
| const struct sockaddr* sa = ainfo->ai_addr; |
| const struct sockaddr_in6* sa_in6 = (const struct sockaddr_in6*) sa; |
| EXPECT_TRUE(htons(kPortnum) == sa_in6->sin6_port); |
| |
| const uint8 expect_addr[] = { |
| 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 |
| }; |
| for (int i = 0; i < 16; i++) { |
| EXPECT_EQ(expect_addr[i], sa_in6->sin6_addr.s6_addr[i]); |
| } |
| } |
| |
| TEST_F(HostResolverImplTest, EmptyHost) { |
| scoped_refptr<RuleBasedHostResolverProc> resolver_proc( |
| new RuleBasedHostResolverProc(NULL)); |
| resolver_proc->AllowDirectLookup("*"); |
| |
| scoped_ptr<HostResolver> host_resolver( |
| CreateHostResolverImpl(resolver_proc)); |
| AddressList addrlist; |
| const int kPortnum = 5555; |
| HostResolver::RequestInfo info(HostPortPair("", kPortnum)); |
| int err = host_resolver->Resolve(info, &addrlist, NULL, NULL, BoundNetLog()); |
| EXPECT_EQ(ERR_NAME_NOT_RESOLVED, err); |
| } |
| |
| TEST_F(HostResolverImplTest, LongHost) { |
| scoped_refptr<RuleBasedHostResolverProc> resolver_proc( |
| new RuleBasedHostResolverProc(NULL)); |
| resolver_proc->AllowDirectLookup("*"); |
| |
| scoped_ptr<HostResolver> host_resolver( |
| CreateHostResolverImpl(resolver_proc)); |
| AddressList addrlist; |
| const int kPortnum = 5555; |
| std::string hostname(4097, 'a'); |
| HostResolver::RequestInfo info(HostPortPair(hostname, kPortnum)); |
| int err = host_resolver->Resolve(info, &addrlist, NULL, NULL, BoundNetLog()); |
| EXPECT_EQ(ERR_NAME_NOT_RESOLVED, err); |
| } |
| |
| // Helper class used by HostResolverImplTest.DeDupeRequests. It receives request |
| // completion notifications for all the resolves, so it can tally up and |
| // determine when we are done. |
| class DeDupeRequestsVerifier : public ResolveRequest::Delegate { |
| public: |
| explicit DeDupeRequestsVerifier(CapturingHostResolverProc* resolver_proc) |
| : count_a_(0), count_b_(0), resolver_proc_(resolver_proc) {} |
| |
| // The test does 5 resolves (which can complete in any order). |
| virtual void OnCompleted(ResolveRequest* resolve) { |
| // Tally up how many requests we have seen. |
| if (resolve->hostname() == "a") { |
| count_a_++; |
| } else if (resolve->hostname() == "b") { |
| count_b_++; |
| } else { |
| FAIL() << "Unexpected hostname: " << resolve->hostname(); |
| } |
| |
| // Check that the port was set correctly. |
| EXPECT_EQ(resolve->port(), resolve->addrlist().GetPort()); |
| |
| // Check whether all the requests have finished yet. |
| int total_completions = count_a_ + count_b_; |
| if (total_completions == 5) { |
| EXPECT_EQ(2, count_a_); |
| EXPECT_EQ(3, count_b_); |
| |
| // The resolver_proc should have been called only twice -- once with "a", |
| // once with "b". |
| CapturingHostResolverProc::CaptureList capture_list = |
| resolver_proc_->GetCaptureList(); |
| EXPECT_EQ(2U, capture_list.size()); |
| |
| // End this test, we are done. |
| MessageLoop::current()->Quit(); |
| } |
| } |
| |
| private: |
| int count_a_; |
| int count_b_; |
| CapturingHostResolverProc* resolver_proc_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DeDupeRequestsVerifier); |
| }; |
| |
| TEST_F(HostResolverImplTest, DeDupeRequests) { |
| // Use a capturing resolver_proc, since the verifier needs to know what calls |
| // reached Resolve(). Also, the capturing resolver_proc is initially blocked. |
| scoped_refptr<CapturingHostResolverProc> resolver_proc( |
| new CapturingHostResolverProc(NULL)); |
| |
| scoped_ptr<HostResolver> host_resolver( |
| CreateHostResolverImpl(resolver_proc)); |
| |
| // The class will receive callbacks for when each resolve completes. It |
| // checks that the right things happened. |
| DeDupeRequestsVerifier verifier(resolver_proc.get()); |
| |
| // Start 5 requests, duplicating hosts "a" and "b". Since the resolver_proc is |
| // blocked, these should all pile up until we signal it. |
| |
| ResolveRequest req1(host_resolver.get(), "a", 80, &verifier); |
| ResolveRequest req2(host_resolver.get(), "b", 80, &verifier); |
| ResolveRequest req3(host_resolver.get(), "b", 81, &verifier); |
| ResolveRequest req4(host_resolver.get(), "a", 82, &verifier); |
| ResolveRequest req5(host_resolver.get(), "b", 83, &verifier); |
| |
| // Ready, Set, GO!!! |
| resolver_proc->Signal(); |
| |
| // |verifier| will send quit message once all the requests have finished. |
| MessageLoop::current()->Run(); |
| } |
| |
| // Helper class used by HostResolverImplTest.CancelMultipleRequests. |
| class CancelMultipleRequestsVerifier : public ResolveRequest::Delegate { |
| public: |
| CancelMultipleRequestsVerifier() {} |
| |
| // The cancels kill all but one request. |
| virtual void OnCompleted(ResolveRequest* resolve) { |
| EXPECT_EQ("a", resolve->hostname()); |
| EXPECT_EQ(82, resolve->port()); |
| |
| // Check that the port was set correctly. |
| EXPECT_EQ(resolve->port(), resolve->addrlist().GetPort()); |
| |
| // End this test, we are done. |
| MessageLoop::current()->Quit(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(CancelMultipleRequestsVerifier); |
| }; |
| |
| TEST_F(HostResolverImplTest, CancelMultipleRequests) { |
| // Use a capturing resolver_proc, since the verifier needs to know what calls |
| // reached Resolver(). Also, the capturing resolver_proc is initially |
| // blocked. |
| scoped_refptr<CapturingHostResolverProc> resolver_proc( |
| new CapturingHostResolverProc(NULL)); |
| |
| scoped_ptr<HostResolver> host_resolver( |
| CreateHostResolverImpl(resolver_proc)); |
| |
| // The class will receive callbacks for when each resolve completes. It |
| // checks that the right things happened. |
| CancelMultipleRequestsVerifier verifier; |
| |
| // Start 5 requests, duplicating hosts "a" and "b". Since the resolver_proc is |
| // blocked, these should all pile up until we signal it. |
| |
| ResolveRequest req1(host_resolver.get(), "a", 80, &verifier); |
| ResolveRequest req2(host_resolver.get(), "b", 80, &verifier); |
| ResolveRequest req3(host_resolver.get(), "b", 81, &verifier); |
| ResolveRequest req4(host_resolver.get(), "a", 82, &verifier); |
| ResolveRequest req5(host_resolver.get(), "b", 83, &verifier); |
| |
| // Cancel everything except request 4. |
| req1.Cancel(); |
| req2.Cancel(); |
| req3.Cancel(); |
| req5.Cancel(); |
| |
| // Ready, Set, GO!!! |
| resolver_proc->Signal(); |
| |
| // |verifier| will send quit message once all the requests have finished. |
| MessageLoop::current()->Run(); |
| } |
| |
| // Helper class used by HostResolverImplTest.CancelWithinCallback. |
| class CancelWithinCallbackVerifier : public ResolveRequest::Delegate { |
| public: |
| CancelWithinCallbackVerifier() |
| : req_to_cancel1_(NULL), req_to_cancel2_(NULL), num_completions_(0) { |
| } |
| |
| virtual void OnCompleted(ResolveRequest* resolve) { |
| num_completions_++; |
| |
| // Port 80 is the first request that the callback will be invoked for. |
| // While we are executing within that callback, cancel the other requests |
| // in the job and start another request. |
| if (80 == resolve->port()) { |
| EXPECT_EQ("a", resolve->hostname()); |
| |
| req_to_cancel1_->Cancel(); |
| req_to_cancel2_->Cancel(); |
| |
| // Start a request (so we can make sure the canceled requests don't |
| // complete before "finalrequest" finishes. |
| final_request_.reset(new ResolveRequest( |
| resolve->resolver(), "finalrequest", 70, this)); |
| |
| } else if (83 == resolve->port()) { |
| EXPECT_EQ("a", resolve->hostname()); |
| } else if (resolve->hostname() == "finalrequest") { |
| EXPECT_EQ(70, resolve->addrlist().GetPort()); |
| |
| // End this test, we are done. |
| MessageLoop::current()->Quit(); |
| } else { |
| FAIL() << "Unexpected completion: " << resolve->hostname() << ", " |
| << resolve->port(); |
| } |
| } |
| |
| void SetRequestsToCancel(ResolveRequest* req_to_cancel1, |
| ResolveRequest* req_to_cancel2) { |
| req_to_cancel1_ = req_to_cancel1; |
| req_to_cancel2_ = req_to_cancel2; |
| } |
| |
| private: |
| scoped_ptr<ResolveRequest> final_request_; |
| ResolveRequest* req_to_cancel1_; |
| ResolveRequest* req_to_cancel2_; |
| int num_completions_; |
| DISALLOW_COPY_AND_ASSIGN(CancelWithinCallbackVerifier); |
| }; |
| |
| TEST_F(HostResolverImplTest, CancelWithinCallback) { |
| // Use a capturing resolver_proc, since the verifier needs to know what calls |
| // reached Resolver(). Also, the capturing resolver_proc is initially |
| // blocked. |
| scoped_refptr<CapturingHostResolverProc> resolver_proc( |
| new CapturingHostResolverProc(NULL)); |
| |
| scoped_ptr<HostResolver> host_resolver( |
| CreateHostResolverImpl(resolver_proc)); |
| |
| // The class will receive callbacks for when each resolve completes. It |
| // checks that the right things happened. |
| CancelWithinCallbackVerifier verifier; |
| |
| // Start 4 requests, duplicating hosts "a". Since the resolver_proc is |
| // blocked, these should all pile up until we signal it. |
| |
| ResolveRequest req1(host_resolver.get(), "a", 80, &verifier); |
| ResolveRequest req2(host_resolver.get(), "a", 81, &verifier); |
| ResolveRequest req3(host_resolver.get(), "a", 82, &verifier); |
| ResolveRequest req4(host_resolver.get(), "a", 83, &verifier); |
| |
| // Once "a:80" completes, it will cancel "a:81" and "a:82". |
| verifier.SetRequestsToCancel(&req2, &req3); |
| |
| // Ready, Set, GO!!! |
| resolver_proc->Signal(); |
| |
| // |verifier| will send quit message once all the requests have finished. |
| MessageLoop::current()->Run(); |
| } |
| |
| // Helper class used by HostResolverImplTest.DeleteWithinCallback. |
| class DeleteWithinCallbackVerifier : public ResolveRequest::Delegate { |
| public: |
| // |host_resolver| is the resolver that the the resolve requests were started |
| // with. |
| explicit DeleteWithinCallbackVerifier(HostResolver* host_resolver) |
| : host_resolver_(host_resolver) {} |
| |
| virtual void OnCompleted(ResolveRequest* resolve) { |
| EXPECT_EQ("a", resolve->hostname()); |
| EXPECT_EQ(80, resolve->port()); |
| |
| // Deletes the host resolver. |
| host_resolver_.reset(); |
| |
| // Quit after returning from OnCompleted (to give it a chance at |
| // incorrectly running the cancelled tasks). |
| MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask()); |
| } |
| |
| private: |
| scoped_ptr<HostResolver> host_resolver_; |
| DISALLOW_COPY_AND_ASSIGN(DeleteWithinCallbackVerifier); |
| }; |
| |
| TEST_F(HostResolverImplTest, DeleteWithinCallback) { |
| // Use a capturing resolver_proc, since the verifier needs to know what calls |
| // reached Resolver(). Also, the capturing resolver_proc is initially |
| // blocked. |
| scoped_refptr<CapturingHostResolverProc> resolver_proc( |
| new CapturingHostResolverProc(NULL)); |
| |
| // The class will receive callbacks for when each resolve completes. It |
| // checks that the right things happened. Note that the verifier holds the |
| // only reference to |host_resolver|, so it can delete it within callback. |
| HostResolver* host_resolver = |
| CreateHostResolverImpl(resolver_proc); |
| DeleteWithinCallbackVerifier verifier(host_resolver); |
| |
| // Start 4 requests, duplicating hosts "a". Since the resolver_proc is |
| // blocked, these should all pile up until we signal it. |
| |
| ResolveRequest req1(host_resolver, "a", 80, &verifier); |
| ResolveRequest req2(host_resolver, "a", 81, &verifier); |
| ResolveRequest req3(host_resolver, "a", 82, &verifier); |
| ResolveRequest req4(host_resolver, "a", 83, &verifier); |
| |
| // Ready, Set, GO!!! |
| resolver_proc->Signal(); |
| |
| // |verifier| will send quit message once all the requests have finished. |
| MessageLoop::current()->Run(); |
| } |
| |
| // Helper class used by HostResolverImplTest.StartWithinCallback. |
| class StartWithinCallbackVerifier : public ResolveRequest::Delegate { |
| public: |
| StartWithinCallbackVerifier() : num_requests_(0) {} |
| |
| virtual void OnCompleted(ResolveRequest* resolve) { |
| EXPECT_EQ("a", resolve->hostname()); |
| |
| if (80 == resolve->port()) { |
| // On completing the first request, start another request for "a". |
| // Since caching is disabled, this will result in another async request. |
| final_request_.reset(new ResolveRequest( |
| resolve->resolver(), "a", 70, this)); |
| } |
| if (++num_requests_ == 5) { |
| // Test is done. |
| MessageLoop::current()->Quit(); |
| } |
| } |
| |
| private: |
| int num_requests_; |
| scoped_ptr<ResolveRequest> final_request_; |
| DISALLOW_COPY_AND_ASSIGN(StartWithinCallbackVerifier); |
| }; |
| |
| TEST_F(HostResolverImplTest, StartWithinCallback) { |
| // Use a capturing resolver_proc, since the verifier needs to know what calls |
| // reached Resolver(). Also, the capturing resolver_proc is initially |
| // blocked. |
| scoped_refptr<CapturingHostResolverProc> resolver_proc( |
| new CapturingHostResolverProc(NULL)); |
| |
| // Turn off caching for this host resolver. |
| scoped_ptr<HostResolver> host_resolver( |
| new HostResolverImpl(resolver_proc, NULL, kMaxJobs, NULL)); |
| |
| // The class will receive callbacks for when each resolve completes. It |
| // checks that the right things happened. |
| StartWithinCallbackVerifier verifier; |
| |
| // Start 4 requests, duplicating hosts "a". Since the resolver_proc is |
| // blocked, these should all pile up until we signal it. |
| |
| ResolveRequest req1(host_resolver.get(), "a", 80, &verifier); |
| ResolveRequest req2(host_resolver.get(), "a", 81, &verifier); |
| ResolveRequest req3(host_resolver.get(), "a", 82, &verifier); |
| ResolveRequest req4(host_resolver.get(), "a", 83, &verifier); |
| |
| // Ready, Set, GO!!! |
| resolver_proc->Signal(); |
| |
| // |verifier| will send quit message once all the requests have finished. |
| MessageLoop::current()->Run(); |
| } |
| |
| // Helper class used by HostResolverImplTest.BypassCache. |
| class BypassCacheVerifier : public ResolveRequest::Delegate { |
| public: |
| BypassCacheVerifier() {} |
| |
| virtual void OnCompleted(ResolveRequest* resolve) { |
| EXPECT_EQ("a", resolve->hostname()); |
| HostResolver* resolver = resolve->resolver(); |
| |
| if (80 == resolve->port()) { |
| // On completing the first request, start another request for "a". |
| // Since caching is enabled, this should complete synchronously. |
| |
| // Note that |junk_callback| shouldn't be used since we are going to |
| // complete synchronously. We can't specify NULL though since that would |
| // mean synchronous mode so we give it a value of 1. |
| CompletionCallback* junk_callback = |
| reinterpret_cast<CompletionCallback*> (1); |
| AddressList addrlist; |
| |
| HostResolver::RequestInfo info(HostPortPair("a", 70)); |
| int error = resolver->Resolve(info, &addrlist, junk_callback, NULL, |
| BoundNetLog()); |
| EXPECT_EQ(OK, error); |
| |
| // Ok good. Now make sure that if we ask to bypass the cache, it can no |
| // longer service the request synchronously. |
| info = HostResolver::RequestInfo(HostPortPair("a", 71)); |
| info.set_allow_cached_response(false); |
| final_request_.reset(new ResolveRequest(resolver, info, this)); |
| } else if (71 == resolve->port()) { |
| // Test is done. |
| MessageLoop::current()->Quit(); |
| } else { |
| FAIL() << "Unexpected port number"; |
| } |
| } |
| |
| private: |
| scoped_ptr<ResolveRequest> final_request_; |
| DISALLOW_COPY_AND_ASSIGN(BypassCacheVerifier); |
| }; |
| |
| TEST_F(HostResolverImplTest, BypassCache) { |
| scoped_ptr<HostResolver> host_resolver( |
| CreateHostResolverImpl(NULL)); |
| |
| // The class will receive callbacks for when each resolve completes. It |
| // checks that the right things happened. |
| BypassCacheVerifier verifier; |
| |
| // Start a request. |
| ResolveRequest req1(host_resolver.get(), "a", 80, &verifier); |
| |
| // |verifier| will send quit message once all the requests have finished. |
| MessageLoop::current()->Run(); |
| } |
| |
| bool operator==(const HostResolver::RequestInfo& a, |
| const HostResolver::RequestInfo& b) { |
| return a.hostname() == b.hostname() && |
| a.port() == b.port() && |
| a.allow_cached_response() == b.allow_cached_response() && |
| a.priority() == b.priority() && |
| a.is_speculative() == b.is_speculative() && |
| a.referrer() == b.referrer(); |
| } |
| |
| // Observer that just makes note of how it was called. The test code can then |
| // inspect to make sure it was called with the right parameters. |
| class CapturingObserver : public HostResolver::Observer { |
| public: |
| // DnsResolutionObserver methods: |
| virtual void OnStartResolution(int id, |
| const HostResolver::RequestInfo& info) { |
| start_log.push_back(StartOrCancelEntry(id, info)); |
| } |
| |
| virtual void OnFinishResolutionWithStatus( |
| int id, |
| bool was_resolved, |
| const HostResolver::RequestInfo& info) { |
| finish_log.push_back(FinishEntry(id, was_resolved, info)); |
| } |
| |
| virtual void OnCancelResolution(int id, |
| const HostResolver::RequestInfo& info) { |
| cancel_log.push_back(StartOrCancelEntry(id, info)); |
| } |
| |
| // Tuple (id, info). |
| struct StartOrCancelEntry { |
| StartOrCancelEntry(int id, const HostResolver::RequestInfo& info) |
| : id(id), info(info) {} |
| |
| bool operator==(const StartOrCancelEntry& other) const { |
| return id == other.id && info == other.info; |
| } |
| |
| int id; |
| HostResolver::RequestInfo info; |
| }; |
| |
| // Tuple (id, was_resolved, info). |
| struct FinishEntry { |
| FinishEntry(int id, bool was_resolved, |
| const HostResolver::RequestInfo& info) |
| : id(id), was_resolved(was_resolved), info(info) {} |
| |
| bool operator==(const FinishEntry& other) const { |
| return id == other.id && |
| was_resolved == other.was_resolved && |
| info == other.info; |
| } |
| |
| int id; |
| bool was_resolved; |
| HostResolver::RequestInfo info; |
| }; |
| |
| std::vector<StartOrCancelEntry> start_log; |
| std::vector<FinishEntry> finish_log; |
| std::vector<StartOrCancelEntry> cancel_log; |
| }; |
| |
| // Test that registering, unregistering, and notifying of observers works. |
| // Does not test the cancellation notification since all resolves are |
| // synchronous. |
| TEST_F(HostResolverImplTest, Observers) { |
| scoped_ptr<HostResolver> host_resolver( |
| CreateHostResolverImpl(NULL)); |
| |
| CapturingObserver observer; |
| |
| host_resolver->AddObserver(&observer); |
| |
| AddressList addrlist; |
| |
| // Resolve "host1". |
| HostResolver::RequestInfo info1(HostPortPair("host1", 70)); |
| CapturingBoundNetLog log(CapturingNetLog::kUnbounded); |
| int rv = host_resolver->Resolve(info1, &addrlist, NULL, NULL, log.bound()); |
| EXPECT_EQ(OK, rv); |
| |
| CapturingNetLog::EntryList entries; |
| log.GetEntries(&entries); |
| |
| EXPECT_EQ(2u, entries.size()); |
| EXPECT_TRUE(LogContainsBeginEvent( |
| entries, 0, NetLog::TYPE_HOST_RESOLVER_IMPL)); |
| EXPECT_TRUE(LogContainsEndEvent( |
| entries, 1, NetLog::TYPE_HOST_RESOLVER_IMPL)); |
| |
| EXPECT_EQ(1U, observer.start_log.size()); |
| EXPECT_EQ(1U, observer.finish_log.size()); |
| EXPECT_EQ(0U, observer.cancel_log.size()); |
| EXPECT_TRUE(observer.start_log[0] == |
| CapturingObserver::StartOrCancelEntry(0, info1)); |
| EXPECT_TRUE(observer.finish_log[0] == |
| CapturingObserver::FinishEntry(0, true, info1)); |
| |
| // Resolve "host1" again -- this time it will be served from cache, but it |
| // should still notify of completion. |
| TestCompletionCallback callback; |
| rv = host_resolver->Resolve(info1, &addrlist, &callback, NULL, BoundNetLog()); |
| ASSERT_EQ(OK, rv); // Should complete synchronously. |
| |
| EXPECT_EQ(2U, observer.start_log.size()); |
| EXPECT_EQ(2U, observer.finish_log.size()); |
| EXPECT_EQ(0U, observer.cancel_log.size()); |
| EXPECT_TRUE(observer.start_log[1] == |
| CapturingObserver::StartOrCancelEntry(1, info1)); |
| EXPECT_TRUE(observer.finish_log[1] == |
| CapturingObserver::FinishEntry(1, true, info1)); |
| |
| // Resolve "host2", setting referrer to "http://foobar.com" |
| HostResolver::RequestInfo info2(HostPortPair("host2", 70)); |
| info2.set_referrer(GURL("http://foobar.com")); |
| rv = host_resolver->Resolve(info2, &addrlist, NULL, NULL, BoundNetLog()); |
| EXPECT_EQ(OK, rv); |
| |
| EXPECT_EQ(3U, observer.start_log.size()); |
| EXPECT_EQ(3U, observer.finish_log.size()); |
| EXPECT_EQ(0U, observer.cancel_log.size()); |
| EXPECT_TRUE(observer.start_log[2] == |
| CapturingObserver::StartOrCancelEntry(2, info2)); |
| EXPECT_TRUE(observer.finish_log[2] == |
| CapturingObserver::FinishEntry(2, true, info2)); |
| |
| // Unregister the observer. |
| host_resolver->RemoveObserver(&observer); |
| |
| // Resolve "host3" |
| HostResolver::RequestInfo info3(HostPortPair("host3", 70)); |
| host_resolver->Resolve(info3, &addrlist, NULL, NULL, BoundNetLog()); |
| |
| // No effect this time, since observer was removed. |
| EXPECT_EQ(3U, observer.start_log.size()); |
| EXPECT_EQ(3U, observer.finish_log.size()); |
| EXPECT_EQ(0U, observer.cancel_log.size()); |
| } |
| |
| // Tests that observers are sent OnCancelResolution() whenever a request is |
| // cancelled. There are two ways to cancel a request: |
| // (1) Delete the HostResolver while job is outstanding. |
| // (2) Call HostResolver::CancelRequest() while a request is outstanding. |
| TEST_F(HostResolverImplTest, CancellationObserver) { |
| CapturingObserver observer; |
| { |
| // Create a host resolver and attach an observer. |
| scoped_ptr<HostResolver> host_resolver( |
| CreateHostResolverImpl(NULL)); |
| host_resolver->AddObserver(&observer); |
| |
| TestCompletionCallback callback; |
| |
| EXPECT_EQ(0U, observer.start_log.size()); |
| EXPECT_EQ(0U, observer.finish_log.size()); |
| EXPECT_EQ(0U, observer.cancel_log.size()); |
| |
| // Start an async resolve for (host1:70). |
| HostResolver::RequestInfo info1(HostPortPair("host1", 70)); |
| HostResolver::RequestHandle req = NULL; |
| AddressList addrlist; |
| int rv = host_resolver->Resolve(info1, &addrlist, &callback, &req, |
| BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| EXPECT_TRUE(NULL != req); |
| |
| EXPECT_EQ(1U, observer.start_log.size()); |
| EXPECT_EQ(0U, observer.finish_log.size()); |
| EXPECT_EQ(0U, observer.cancel_log.size()); |
| |
| EXPECT_TRUE(observer.start_log[0] == |
| CapturingObserver::StartOrCancelEntry(0, info1)); |
| |
| // Cancel the request. |
| host_resolver->CancelRequest(req); |
| |
| EXPECT_EQ(1U, observer.start_log.size()); |
| EXPECT_EQ(0U, observer.finish_log.size()); |
| EXPECT_EQ(1U, observer.cancel_log.size()); |
| |
| EXPECT_TRUE(observer.cancel_log[0] == |
| CapturingObserver::StartOrCancelEntry(0, info1)); |
| |
| // Start an async request for (host2:60) |
| HostResolver::RequestInfo info2(HostPortPair("host2", 60)); |
| rv = host_resolver->Resolve(info2, &addrlist, &callback, NULL, |
| BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| EXPECT_TRUE(NULL != req); |
| |
| EXPECT_EQ(2U, observer.start_log.size()); |
| EXPECT_EQ(0U, observer.finish_log.size()); |
| EXPECT_EQ(1U, observer.cancel_log.size()); |
| |
| EXPECT_TRUE(observer.start_log[1] == |
| CapturingObserver::StartOrCancelEntry(1, info2)); |
| |
| // Upon exiting this scope, HostResolver is destroyed, so all requests are |
| // implicitly cancelled. |
| } |
| |
| // Check that destroying the HostResolver sent a notification for |
| // cancellation of host2:60 request. |
| |
| EXPECT_EQ(2U, observer.start_log.size()); |
| EXPECT_EQ(0U, observer.finish_log.size()); |
| EXPECT_EQ(2U, observer.cancel_log.size()); |
| |
| HostResolver::RequestInfo info(HostPortPair("host2", 60)); |
| EXPECT_TRUE(observer.cancel_log[1] == |
| CapturingObserver::StartOrCancelEntry(1, info)); |
| } |
| |
| // Test that IP address changes flush the cache. |
| TEST_F(HostResolverImplTest, FlushCacheOnIPAddressChange) { |
| scoped_ptr<HostResolver> host_resolver( |
| new HostResolverImpl(NULL, CreateDefaultCache(), kMaxJobs, NULL)); |
| |
| AddressList addrlist; |
| |
| // Resolve "host1". |
| HostResolver::RequestInfo info1(HostPortPair("host1", 70)); |
| TestCompletionCallback callback; |
| int rv = host_resolver->Resolve(info1, &addrlist, &callback, NULL, |
| BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| EXPECT_EQ(OK, callback.WaitForResult()); |
| |
| // Resolve "host1" again -- this time it will be served from cache, but it |
| // should still notify of completion. |
| rv = host_resolver->Resolve(info1, &addrlist, &callback, NULL, BoundNetLog()); |
| ASSERT_EQ(OK, rv); // Should complete synchronously. |
| |
| // Flush cache by triggering an IP address change. |
| NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); |
| MessageLoop::current()->RunAllPending(); // Notification happens async. |
| |
| // Resolve "host1" again -- this time it won't be served from cache, so it |
| // will complete asynchronously. |
| rv = host_resolver->Resolve(info1, &addrlist, &callback, NULL, BoundNetLog()); |
| ASSERT_EQ(ERR_IO_PENDING, rv); // Should complete asynchronously. |
| EXPECT_EQ(OK, callback.WaitForResult()); |
| } |
| |
| // Test that IP address changes send ERR_ABORTED to pending requests. |
| TEST_F(HostResolverImplTest, AbortOnIPAddressChanged) { |
| scoped_refptr<WaitingHostResolverProc> resolver_proc( |
| new WaitingHostResolverProc(NULL)); |
| HostCache* cache = CreateDefaultCache(); |
| scoped_ptr<HostResolver> host_resolver( |
| new HostResolverImpl(resolver_proc, cache, kMaxJobs, NULL)); |
| |
| // Resolve "host1". |
| HostResolver::RequestInfo info(HostPortPair("host1", 70)); |
| TestCompletionCallback callback; |
| AddressList addrlist; |
| int rv = host_resolver->Resolve(info, &addrlist, &callback, NULL, |
| BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| // Triggering an IP address change. |
| NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); |
| MessageLoop::current()->RunAllPending(); // Notification happens async. |
| resolver_proc->Signal(); |
| |
| EXPECT_EQ(ERR_ABORTED, callback.WaitForResult()); |
| EXPECT_EQ(0u, cache->size()); |
| } |
| |
| // Obey pool constraints after IP address has changed. |
| TEST_F(HostResolverImplTest, ObeyPoolConstraintsAfterIPAddressChange) { |
| scoped_refptr<WaitingHostResolverProc> resolver_proc( |
| new WaitingHostResolverProc(NULL)); |
| scoped_ptr<MockHostResolver> host_resolver(new MockHostResolver()); |
| host_resolver->Reset(resolver_proc); |
| |
| const size_t kMaxOutstandingJobs = 1u; |
| const size_t kMaxPendingRequests = 1000000u; // not relevant. |
| host_resolver->SetPoolConstraints(HostResolverImpl::POOL_NORMAL, |
| kMaxOutstandingJobs, |
| kMaxPendingRequests); |
| |
| // Resolve "host1". |
| HostResolver::RequestInfo info(HostPortPair("host1", 70)); |
| TestCompletionCallback callback; |
| AddressList addrlist; |
| int rv = host_resolver->Resolve(info, &addrlist, &callback, NULL, |
| BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| // Triggering an IP address change. |
| NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); |
| MessageLoop::current()->RunAllPending(); // Notification happens async. |
| resolver_proc->Signal(); |
| |
| EXPECT_EQ(ERR_ABORTED, callback.WaitForResult()); |
| |
| // Don't bother with WaitingHostResolverProc anymore. |
| host_resolver->Reset(NULL); |
| |
| rv = host_resolver->Resolve(info, &addrlist, &callback, NULL, |
| BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| EXPECT_EQ(OK, callback.WaitForResult()); |
| } |
| |
| class ResolveWithinCallback : public CallbackRunner< Tuple1<int> > { |
| public: |
| ResolveWithinCallback( |
| MockHostResolver* host_resolver, |
| const HostResolver::RequestInfo& info) |
| : host_resolver_(host_resolver), |
| info_(info) { |
| DCHECK(host_resolver); |
| } |
| |
| virtual void RunWithParams(const Tuple1<int>& params) { |
| // Ditch the WaitingHostResolverProc so that the subsequent request |
| // succeeds. |
| host_resolver_->Reset(NULL); |
| callback_.RunWithParams(params); |
| EXPECT_EQ(ERR_IO_PENDING, |
| host_resolver_->Resolve(info_, &addrlist_, &nested_callback_, |
| NULL, BoundNetLog())); |
| } |
| |
| int WaitForResult() { |
| return callback_.WaitForResult(); |
| } |
| |
| int WaitForNestedResult() { |
| return nested_callback_.WaitForResult(); |
| } |
| |
| private: |
| MockHostResolver* const host_resolver_; |
| const HostResolver::RequestInfo info_; |
| AddressList addrlist_; |
| TestCompletionCallback callback_; |
| TestCompletionCallback nested_callback_; |
| }; |
| |
| TEST_F(HostResolverImplTest, OnlyAbortExistingRequestsOnIPAddressChange) { |
| scoped_refptr<WaitingHostResolverProc> resolver_proc( |
| new WaitingHostResolverProc(NULL)); |
| scoped_ptr<MockHostResolver> host_resolver(new MockHostResolver()); |
| host_resolver->Reset(resolver_proc); |
| |
| // Resolve "host1". |
| HostResolver::RequestInfo info(HostPortPair("host1", 70)); |
| ResolveWithinCallback callback(host_resolver.get(), info); |
| AddressList addrlist; |
| int rv = host_resolver->Resolve(info, &addrlist, &callback, NULL, |
| BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| |
| // Triggering an IP address change. |
| NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); |
| MessageLoop::current()->RunAllPending(); // Notification happens async. |
| |
| EXPECT_EQ(ERR_ABORTED, callback.WaitForResult()); |
| resolver_proc->Signal(); |
| EXPECT_EQ(OK, callback.WaitForNestedResult()); |
| } |
| |
| // Tests that when the maximum threads is set to 1, requests are dequeued |
| // in order of priority. |
| TEST_F(HostResolverImplTest, HigherPriorityRequestsStartedFirst) { |
| scoped_refptr<CapturingHostResolverProc> resolver_proc( |
| new CapturingHostResolverProc(NULL)); |
| |
| // This HostResolverImpl will only allow 1 outstanding resolve at a time. |
| size_t kMaxJobs = 1u; |
| scoped_ptr<HostResolver> host_resolver( |
| new HostResolverImpl(resolver_proc, CreateDefaultCache(), kMaxJobs, |
| NULL)); |
| |
| CapturingObserver observer; |
| host_resolver->AddObserver(&observer); |
| |
| // Note that at this point the CapturingHostResolverProc is blocked, so any |
| // requests we make will not complete. |
| |
| HostResolver::RequestInfo req[] = { |
| CreateResolverRequest("req0", LOW), |
| CreateResolverRequest("req1", MEDIUM), |
| CreateResolverRequest("req2", MEDIUM), |
| CreateResolverRequest("req3", LOW), |
| CreateResolverRequest("req4", HIGHEST), |
| CreateResolverRequest("req5", LOW), |
| CreateResolverRequest("req6", LOW), |
| CreateResolverRequest("req5", HIGHEST), |
| }; |
| |
| TestCompletionCallback callback[arraysize(req)]; |
| AddressList addrlist[arraysize(req)]; |
| |
| // Start all of the requests. |
| for (size_t i = 0; i < arraysize(req); ++i) { |
| int rv = host_resolver->Resolve(req[i], &addrlist[i], |
| &callback[i], NULL, BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| } |
| |
| // Unblock the resolver thread so the requests can run. |
| resolver_proc->Signal(); |
| |
| // Wait for all the requests to complete succesfully. |
| for (size_t i = 0; i < arraysize(req); ++i) { |
| EXPECT_EQ(OK, callback[i].WaitForResult()) << "i=" << i; |
| } |
| |
| host_resolver->RemoveObserver(&observer); |
| |
| // Since we have restricted to a single concurrent thread in the jobpool, |
| // the requests should complete in order of priority (with the exception |
| // of the first request, which gets started right away, since there is |
| // nothing outstanding). |
| CapturingHostResolverProc::CaptureList capture_list = |
| resolver_proc->GetCaptureList(); |
| ASSERT_EQ(7u, capture_list.size()); |
| |
| EXPECT_EQ("req0", capture_list[0].hostname); |
| EXPECT_EQ("req4", capture_list[1].hostname); |
| EXPECT_EQ("req5", capture_list[2].hostname); |
| EXPECT_EQ("req1", capture_list[3].hostname); |
| EXPECT_EQ("req2", capture_list[4].hostname); |
| EXPECT_EQ("req3", capture_list[5].hostname); |
| EXPECT_EQ("req6", capture_list[6].hostname); |
| |
| // Also check using the observer's trace. |
| EXPECT_EQ(8U, observer.start_log.size()); |
| EXPECT_EQ(8U, observer.finish_log.size()); |
| EXPECT_EQ(0U, observer.cancel_log.size()); |
| |
| EXPECT_EQ("req0", observer.finish_log[0].info.hostname()); |
| EXPECT_EQ("req4", observer.finish_log[1].info.hostname()); |
| |
| // There were two requests for "req5". The highest priority |
| // one should have been dispatched earlier. |
| EXPECT_EQ("req5", observer.finish_log[2].info.hostname()); |
| EXPECT_EQ("req5", observer.finish_log[3].info.hostname()); |
| EXPECT_EQ(HIGHEST, observer.finish_log[2].info.priority()); |
| EXPECT_EQ(LOW, observer.finish_log[3].info.priority()); |
| |
| EXPECT_EQ("req1", observer.finish_log[4].info.hostname()); |
| EXPECT_EQ("req2", observer.finish_log[5].info.hostname()); |
| EXPECT_EQ("req3", observer.finish_log[6].info.hostname()); |
| EXPECT_EQ("req6", observer.finish_log[7].info.hostname()); |
| } |
| |
| // Try cancelling a request which has not been attached to a job yet. |
| TEST_F(HostResolverImplTest, CancelPendingRequest) { |
| scoped_refptr<CapturingHostResolverProc> resolver_proc( |
| new CapturingHostResolverProc(NULL)); |
| |
| // This HostResolverImpl will only allow 1 outstanding resolve at a time. |
| const size_t kMaxJobs = 1u; |
| scoped_ptr<HostResolver> host_resolver( |
| new HostResolverImpl(resolver_proc, CreateDefaultCache(), kMaxJobs, |
| NULL)); |
| |
| // Note that at this point the CapturingHostResolverProc is blocked, so any |
| // requests we make will not complete. |
| |
| HostResolver::RequestInfo req[] = { |
| CreateResolverRequest("req0", LOWEST), |
| CreateResolverRequest("req1", HIGHEST), // Will cancel. |
| CreateResolverRequest("req2", MEDIUM), |
| CreateResolverRequest("req3", LOW), |
| CreateResolverRequest("req4", HIGHEST), // Will cancel. |
| CreateResolverRequest("req5", LOWEST), // Will cancel. |
| CreateResolverRequest("req6", MEDIUM), |
| }; |
| |
| TestCompletionCallback callback[arraysize(req)]; |
| AddressList addrlist[arraysize(req)]; |
| HostResolver::RequestHandle handle[arraysize(req)]; |
| |
| // Start all of the requests. |
| for (size_t i = 0; i < arraysize(req); ++i) { |
| int rv = host_resolver->Resolve(req[i], &addrlist[i], |
| &callback[i], &handle[i], BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv); |
| } |
| |
| // Cancel some requests |
| host_resolver->CancelRequest(handle[1]); |
| host_resolver->CancelRequest(handle[4]); |
| host_resolver->CancelRequest(handle[5]); |
| handle[1] = handle[4] = handle[5] = NULL; |
| |
| // Unblock the resolver thread so the requests can run. |
| resolver_proc->Signal(); |
| |
| // Wait for all the requests to complete succesfully. |
| for (size_t i = 0; i < arraysize(req); ++i) { |
| if (!handle[i]) |
| continue; // Don't wait for the requests we cancelled. |
| EXPECT_EQ(OK, callback[i].WaitForResult()); |
| } |
| |
| // Verify that they called out the the resolver proc (which runs on the |
| // resolver thread) in the expected order. |
| CapturingHostResolverProc::CaptureList capture_list = |
| resolver_proc->GetCaptureList(); |
| ASSERT_EQ(4u, capture_list.size()); |
| |
| EXPECT_EQ("req0", capture_list[0].hostname); |
| EXPECT_EQ("req2", capture_list[1].hostname); |
| EXPECT_EQ("req6", capture_list[2].hostname); |
| EXPECT_EQ("req3", capture_list[3].hostname); |
| } |
| |
| // Test that when too many requests are enqueued, old ones start to be aborted. |
| TEST_F(HostResolverImplTest, QueueOverflow) { |
| scoped_refptr<CapturingHostResolverProc> resolver_proc( |
| new CapturingHostResolverProc(NULL)); |
| |
| // This HostResolverImpl will only allow 1 outstanding resolve at a time. |
| const size_t kMaxOutstandingJobs = 1u; |
| scoped_ptr<HostResolverImpl> host_resolver(new HostResolverImpl( |
| resolver_proc, CreateDefaultCache(), kMaxOutstandingJobs, NULL)); |
| |
| // Only allow up to 3 requests to be enqueued at a time. |
| const size_t kMaxPendingRequests = 3u; |
| host_resolver->SetPoolConstraints(HostResolverImpl::POOL_NORMAL, |
| kMaxOutstandingJobs, |
| kMaxPendingRequests); |
| |
| // Note that at this point the CapturingHostResolverProc is blocked, so any |
| // requests we make will not complete. |
| |
| HostResolver::RequestInfo req[] = { |
| CreateResolverRequest("req0", LOWEST), |
| CreateResolverRequest("req1", HIGHEST), |
| CreateResolverRequest("req2", MEDIUM), |
| CreateResolverRequest("req3", MEDIUM), |
| |
| // At this point, there are 3 enqueued requests. |
| // Insertion of subsequent requests will cause evictions |
| // based on priority. |
| |
| CreateResolverRequest("req4", LOW), // Evicts itself! |
| CreateResolverRequest("req5", MEDIUM), // Evicts req3 |
| CreateResolverRequest("req6", HIGHEST), // Evicts req5. |
| CreateResolverRequest("req7", MEDIUM), // Evicts req2. |
| }; |
| |
| TestCompletionCallback callback[arraysize(req)]; |
| AddressList addrlist[arraysize(req)]; |
| HostResolver::RequestHandle handle[arraysize(req)]; |
| |
| // Start all of the requests. |
| for (size_t i = 0; i < arraysize(req); ++i) { |
| int rv = host_resolver->Resolve(req[i], &addrlist[i], |
| &callback[i], &handle[i], BoundNetLog()); |
| if (i == 4u) |
| EXPECT_EQ(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE, rv); |
| else |
| EXPECT_EQ(ERR_IO_PENDING, rv) << i; |
| } |
| |
| // Unblock the resolver thread so the requests can run. |
| resolver_proc->Signal(); |
| |
| // Requests 3, 5, 2 will have been evicted due to queue overflow. |
| size_t reqs_expected_to_fail[] = { 2, 3, 5 }; |
| for (size_t i = 0; i < arraysize(reqs_expected_to_fail); ++i) { |
| EXPECT_EQ(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE, |
| callback[reqs_expected_to_fail[i]].WaitForResult()); |
| } |
| |
| // The rest should succeed. |
| size_t reqs_expected_to_succeed[] = { 0, 1, 6, 7 }; |
| for (size_t i = 0; i < arraysize(reqs_expected_to_succeed); ++i) { |
| EXPECT_EQ(OK, callback[reqs_expected_to_succeed[i]].WaitForResult()); |
| } |
| |
| // Verify that they called out the the resolver proc (which runs on the |
| // resolver thread) in the expected order. |
| CapturingHostResolverProc::CaptureList capture_list = |
| resolver_proc->GetCaptureList(); |
| ASSERT_EQ(4u, capture_list.size()); |
| |
| EXPECT_EQ("req0", capture_list[0].hostname); |
| EXPECT_EQ("req1", capture_list[1].hostname); |
| EXPECT_EQ("req6", capture_list[2].hostname); |
| EXPECT_EQ("req7", capture_list[3].hostname); |
| } |
| |
| // Tests that after changing the default AddressFamily to IPV4, requests |
| // with UNSPECIFIED address family map to IPV4. |
| TEST_F(HostResolverImplTest, SetDefaultAddressFamily_IPv4) { |
| scoped_refptr<CapturingHostResolverProc> resolver_proc( |
| new CapturingHostResolverProc(new EchoingHostResolverProc)); |
| |
| // This HostResolverImpl will only allow 1 outstanding resolve at a time. |
| const size_t kMaxOutstandingJobs = 1u; |
| scoped_ptr<HostResolverImpl> host_resolver(new HostResolverImpl( |
| resolver_proc, CreateDefaultCache(), kMaxOutstandingJobs, NULL)); |
| |
| host_resolver->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV4); |
| |
| // Note that at this point the CapturingHostResolverProc is blocked, so any |
| // requests we make will not complete. |
| |
| HostResolver::RequestInfo req[] = { |
| CreateResolverRequestForAddressFamily("h1", MEDIUM, |
| ADDRESS_FAMILY_UNSPECIFIED), |
| CreateResolverRequestForAddressFamily("h1", MEDIUM, ADDRESS_FAMILY_IPV4), |
| CreateResolverRequestForAddressFamily("h1", MEDIUM, ADDRESS_FAMILY_IPV6), |
| }; |
| |
| TestCompletionCallback callback[arraysize(req)]; |
| AddressList addrlist[arraysize(req)]; |
| HostResolver::RequestHandle handle[arraysize(req)]; |
| |
| // Start all of the requests. |
| for (size_t i = 0; i < arraysize(req); ++i) { |
| int rv = host_resolver->Resolve(req[i], &addrlist[i], |
| &callback[i], &handle[i], BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv) << i; |
| } |
| |
| // Unblock the resolver thread so the requests can run. |
| resolver_proc->Signal(); |
| |
| // Wait for all the requests to complete. |
| for (size_t i = 0u; i < arraysize(req); ++i) { |
| EXPECT_EQ(OK, callback[i].WaitForResult()); |
| } |
| |
| // Since the requests all had the same priority and we limited the thread |
| // count to 1, they should have completed in the same order as they were |
| // requested. Moreover, request0 and request1 will have been serviced by |
| // the same job. |
| |
| CapturingHostResolverProc::CaptureList capture_list = |
| resolver_proc->GetCaptureList(); |
| ASSERT_EQ(2u, capture_list.size()); |
| |
| EXPECT_EQ("h1", capture_list[0].hostname); |
| EXPECT_EQ(ADDRESS_FAMILY_IPV4, capture_list[0].address_family); |
| |
| EXPECT_EQ("h1", capture_list[1].hostname); |
| EXPECT_EQ(ADDRESS_FAMILY_IPV6, capture_list[1].address_family); |
| |
| // Now check that the correct resolved IP addresses were returned. |
| // Addresses take the form: 192.x.y.z |
| // x = length of hostname |
| // y = ASCII value of hostname[0] |
| // z = value of address family |
| EXPECT_EQ("192.2.104.1", NetAddressToString(addrlist[0].head())); |
| EXPECT_EQ("192.2.104.1", NetAddressToString(addrlist[1].head())); |
| EXPECT_EQ("192.2.104.2", NetAddressToString(addrlist[2].head())); |
| } |
| |
| // This is the exact same test as SetDefaultAddressFamily_IPv4, except the order |
| // of requests 0 and 1 is flipped, and the default is set to IPv6 in place of |
| // IPv4. |
| TEST_F(HostResolverImplTest, SetDefaultAddressFamily_IPv6) { |
| scoped_refptr<CapturingHostResolverProc> resolver_proc( |
| new CapturingHostResolverProc(new EchoingHostResolverProc)); |
| |
| // This HostResolverImpl will only allow 1 outstanding resolve at a time. |
| const size_t kMaxOutstandingJobs = 1u; |
| scoped_ptr<HostResolverImpl> host_resolver(new HostResolverImpl( |
| resolver_proc, CreateDefaultCache(), kMaxOutstandingJobs, NULL)); |
| |
| host_resolver->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV6); |
| |
| // Note that at this point the CapturingHostResolverProc is blocked, so any |
| // requests we make will not complete. |
| |
| HostResolver::RequestInfo req[] = { |
| CreateResolverRequestForAddressFamily("h1", MEDIUM, ADDRESS_FAMILY_IPV6), |
| CreateResolverRequestForAddressFamily("h1", MEDIUM, |
| ADDRESS_FAMILY_UNSPECIFIED), |
| CreateResolverRequestForAddressFamily("h1", MEDIUM, ADDRESS_FAMILY_IPV4), |
| }; |
| |
| TestCompletionCallback callback[arraysize(req)]; |
| AddressList addrlist[arraysize(req)]; |
| HostResolver::RequestHandle handle[arraysize(req)]; |
| |
| // Start all of the requests. |
| for (size_t i = 0; i < arraysize(req); ++i) { |
| int rv = host_resolver->Resolve(req[i], &addrlist[i], |
| &callback[i], &handle[i], BoundNetLog()); |
| EXPECT_EQ(ERR_IO_PENDING, rv) << i; |
| } |
| |
| // Unblock the resolver thread so the requests can run. |
| resolver_proc->Signal(); |
| |
| // Wait for all the requests to complete. |
| for (size_t i = 0u; i < arraysize(req); ++i) { |
| EXPECT_EQ(OK, callback[i].WaitForResult()); |
| } |
| |
| // Since the requests all had the same priority and we limited the thread |
| // count to 1, they should have completed in the same order as they were |
| // requested. Moreover, request0 and request1 will have been serviced by |
| // the same job. |
| |
| CapturingHostResolverProc::CaptureList capture_list = |
| resolver_proc->GetCaptureList(); |
| ASSERT_EQ(2u, capture_list.size()); |
| |
| EXPECT_EQ("h1", capture_list[0].hostname); |
| EXPECT_EQ(ADDRESS_FAMILY_IPV6, capture_list[0].address_family); |
| |
| EXPECT_EQ("h1", capture_list[1].hostname); |
| EXPECT_EQ(ADDRESS_FAMILY_IPV4, capture_list[1].address_family); |
| |
| // Now check that the correct resolved IP addresses were returned. |
| // Addresses take the form: 192.x.y.z |
| // x = length of hostname |
| // y = ASCII value of hostname[0] |
| // z = value of address family |
| EXPECT_EQ("192.2.104.2", NetAddressToString(addrlist[0].head())); |
| EXPECT_EQ("192.2.104.2", NetAddressToString(addrlist[1].head())); |
| EXPECT_EQ("192.2.104.1", NetAddressToString(addrlist[2].head())); |
| } |
| |
| // This tests that the default address family is respected for synchronous |
| // resolutions. |
| TEST_F(HostResolverImplTest, SetDefaultAddressFamily_Synchronous) { |
| scoped_refptr<CapturingHostResolverProc> resolver_proc( |
| new CapturingHostResolverProc(new EchoingHostResolverProc)); |
| |
| const size_t kMaxOutstandingJobs = 10u; |
| scoped_ptr<HostResolverImpl> host_resolver(new HostResolverImpl( |
| resolver_proc, CreateDefaultCache(), kMaxOutstandingJobs, NULL)); |
| |
| host_resolver->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV4); |
| |
| // Unblock the resolver thread so the requests can run. |
| resolver_proc->Signal(); |
| |
| HostResolver::RequestInfo req[] = { |
| CreateResolverRequestForAddressFamily("b", MEDIUM, |
| ADDRESS_FAMILY_UNSPECIFIED), |
| CreateResolverRequestForAddressFamily("b", MEDIUM, ADDRESS_FAMILY_IPV6), |
| CreateResolverRequestForAddressFamily("b", MEDIUM, |
| ADDRESS_FAMILY_UNSPECIFIED), |
| CreateResolverRequestForAddressFamily("b", MEDIUM, ADDRESS_FAMILY_IPV4), |
| }; |
| AddressList addrlist[arraysize(req)]; |
| |
| // Start and run all of the requests synchronously. |
| for (size_t i = 0; i < arraysize(req); ++i) { |
| int rv = host_resolver->Resolve(req[i], &addrlist[i], |
| NULL, NULL, BoundNetLog()); |
| EXPECT_EQ(OK, rv) << i; |
| } |
| |
| // We should have sent 2 requests to the resolver -- |
| // one for (b, IPv4), and one for (b, IPv6). |
| CapturingHostResolverProc::CaptureList capture_list = |
| resolver_proc->GetCaptureList(); |
| ASSERT_EQ(2u, capture_list.size()); |
| |
| EXPECT_EQ("b", capture_list[0].hostname); |
| EXPECT_EQ(ADDRESS_FAMILY_IPV4, capture_list[0].address_family); |
| |
| EXPECT_EQ("b", capture_list[1].hostname); |
| EXPECT_EQ(ADDRESS_FAMILY_IPV6, capture_list[1].address_family); |
| |
| // Now check that the correct resolved IP addresses were returned. |
| // Addresses take the form: 192.x.y.z |
| // x = length of hostname |
| // y = ASCII value of hostname[0] |
| // z = value of address family |
| EXPECT_EQ("192.1.98.1", NetAddressToString(addrlist[0].head())); |
| EXPECT_EQ("192.1.98.2", NetAddressToString(addrlist[1].head())); |
| EXPECT_EQ("192.1.98.1", NetAddressToString(addrlist[2].head())); |
| EXPECT_EQ("192.1.98.1", NetAddressToString(addrlist[3].head())); |
| } |
| |
| TEST_F(HostResolverImplTest, DisallowNonCachedResponses) { |
| AddressList addrlist; |
| const int kPortnum = 80; |
| |
| scoped_refptr<RuleBasedHostResolverProc> resolver_proc( |
| new RuleBasedHostResolverProc(NULL)); |
| resolver_proc->AddRule("just.testing", "192.168.1.42"); |
| |
| scoped_ptr<HostResolver> host_resolver( |
| CreateHostResolverImpl(resolver_proc)); |
| |
| // First hit will miss the cache. |
| HostResolver::RequestInfo info(HostPortPair("just.testing", kPortnum)); |
| info.set_only_use_cached_response(true); |
| CapturingBoundNetLog log(CapturingNetLog::kUnbounded); |
| int err = host_resolver->Resolve(info, &addrlist, NULL, NULL, log.bound()); |
| EXPECT_EQ(ERR_NAME_NOT_RESOLVED, err); |
| |
| // This time, we fetch normally. |
| info.set_only_use_cached_response(false); |
| err = host_resolver->Resolve(info, &addrlist, NULL, NULL, log.bound()); |
| EXPECT_EQ(OK, err); |
| |
| // Now we should be able to fetch from the cache. |
| info.set_only_use_cached_response(true); |
| err = host_resolver->Resolve(info, &addrlist, NULL, NULL, log.bound()); |
| EXPECT_EQ(OK, err); |
| |
| const struct addrinfo* ainfo = addrlist.head(); |
| EXPECT_EQ(static_cast<addrinfo*>(NULL), ainfo->ai_next); |
| EXPECT_EQ(sizeof(struct sockaddr_in), ainfo->ai_addrlen); |
| |
| const struct sockaddr* sa = ainfo->ai_addr; |
| const struct sockaddr_in* sa_in = reinterpret_cast<const sockaddr_in*>(sa); |
| EXPECT_TRUE(htons(kPortnum) == sa_in->sin_port); |
| EXPECT_TRUE(htonl(0xc0a8012a) == sa_in->sin_addr.s_addr); |
| } |
| // TODO(cbentzel): Test a mix of requests with different HostResolverFlags. |
| |
| } // namespace |
| |
| } // namespace net |