| // 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. |
| |
| #include <time.h> |
| |
| #include <algorithm> |
| #include <sstream> |
| #include <string> |
| |
| #include "base/message_loop.h" |
| #include "base/scoped_ptr.h" |
| #include "base/string_util.h" |
| #include "base/timer.h" |
| #include "chrome/browser/chrome_thread.h" |
| #include "chrome/browser/net/dns_global.h" |
| #include "chrome/browser/net/dns_host_info.h" |
| #include "chrome/common/net/dns.h" |
| #include "net/base/address_list.h" |
| #include "net/base/mock_host_resolver.h" |
| #include "net/base/winsock_init.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using base::Time; |
| using base::TimeDelta; |
| |
| namespace chrome_browser_net { |
| |
| class WaitForResolutionHelper; |
| |
| typedef base::RepeatingTimer<WaitForResolutionHelper> HelperTimer; |
| |
| class WaitForResolutionHelper { |
| public: |
| WaitForResolutionHelper(DnsMaster* master, const NameList& hosts, |
| HelperTimer* timer) |
| : master_(master), |
| hosts_(hosts), |
| timer_(timer) { |
| } |
| |
| void Run() { |
| for (NameList::const_iterator i = hosts_.begin(); i != hosts_.end(); ++i) |
| if (master_->GetResolutionDuration(*i) == DnsHostInfo::kNullDuration) |
| return; // We don't have resolution for that host. |
| |
| // When all hostnames have been resolved, exit the loop. |
| timer_->Stop(); |
| MessageLoop::current()->Quit(); |
| delete timer_; |
| delete this; |
| } |
| |
| private: |
| DnsMaster* master_; |
| const NameList hosts_; |
| HelperTimer* timer_; |
| }; |
| |
| class DnsMasterTest : public testing::Test { |
| public: |
| DnsMasterTest() |
| : io_thread_(ChromeThread::IO, &loop_), |
| host_resolver_(new net::MockCachingHostResolver()), |
| default_max_queueing_delay_(TimeDelta::FromMilliseconds( |
| DnsGlobalInit::kMaxPrefetchQueueingDelayMs)) { |
| } |
| |
| protected: |
| virtual void SetUp() { |
| #if defined(OS_WIN) |
| net::EnsureWinsockInit(); |
| #endif |
| // Since we are using a caching HostResolver, the following latencies will |
| // only be incurred by the first request, after which the result will be |
| // cached internally by |host_resolver_|. |
| net::RuleBasedHostResolverProc* rules = host_resolver_->rules(); |
| rules->AddRuleWithLatency("www.google.com", "127.0.0.1", 50); |
| rules->AddRuleWithLatency("gmail.google.com.com", "127.0.0.1", 70); |
| rules->AddRuleWithLatency("mail.google.com", "127.0.0.1", 44); |
| rules->AddRuleWithLatency("gmail.com", "127.0.0.1", 63); |
| } |
| |
| void WaitForResolution(DnsMaster* master, const NameList& hosts) { |
| HelperTimer* timer = new HelperTimer(); |
| timer->Start(TimeDelta::FromMilliseconds(100), |
| new WaitForResolutionHelper(master, hosts, timer), |
| &WaitForResolutionHelper::Run); |
| MessageLoop::current()->Run(); |
| } |
| |
| private: |
| // IMPORTANT: do not move this below |host_resolver_|; the host resolver |
| // must not outlive the message loop, otherwise bad things can happen |
| // (like posting to a deleted message loop). |
| MessageLoop loop_; |
| ChromeThread io_thread_; |
| |
| protected: |
| scoped_refptr<net::MockCachingHostResolver> host_resolver_; |
| |
| // Shorthand to access TimeDelta of DnsGlobalInit::kMaxQueueingDelayMs. |
| // (It would be a static constant... except style rules preclude that :-/ ). |
| const TimeDelta default_max_queueing_delay_; |
| }; |
| |
| //------------------------------------------------------------------------------ |
| |
| TEST_F(DnsMasterTest, StartupShutdownTest) { |
| scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_, |
| default_max_queueing_delay_, |
| DnsGlobalInit::kMaxPrefetchConcurrentLookups); |
| testing_master->Shutdown(); |
| } |
| |
| TEST_F(DnsMasterTest, BenefitLookupTest) { |
| scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_, |
| default_max_queueing_delay_, |
| DnsGlobalInit::kMaxPrefetchConcurrentLookups); |
| |
| std::string goog("www.google.com"), |
| goog2("gmail.google.com.com"), |
| goog3("mail.google.com"), |
| goog4("gmail.com"); |
| DnsHostInfo goog_info, goog2_info, goog3_info, goog4_info; |
| |
| // Simulate getting similar names from a network observer |
| goog_info.SetHostname(goog); |
| goog2_info.SetHostname(goog2); |
| goog3_info.SetHostname(goog3); |
| goog4_info.SetHostname(goog4); |
| |
| goog_info.SetStartedState(); |
| goog2_info.SetStartedState(); |
| goog3_info.SetStartedState(); |
| goog4_info.SetStartedState(); |
| |
| goog_info.SetFinishedState(true); |
| goog2_info.SetFinishedState(true); |
| goog3_info.SetFinishedState(true); |
| goog4_info.SetFinishedState(true); |
| |
| NameList names; |
| names.insert(names.end(), goog); |
| names.insert(names.end(), goog2); |
| names.insert(names.end(), goog3); |
| names.insert(names.end(), goog4); |
| |
| testing_master->ResolveList(names, DnsHostInfo::PAGE_SCAN_MOTIVATED); |
| |
| WaitForResolution(testing_master, names); |
| |
| EXPECT_TRUE(testing_master->WasFound(goog)); |
| EXPECT_TRUE(testing_master->WasFound(goog2)); |
| EXPECT_TRUE(testing_master->WasFound(goog3)); |
| EXPECT_TRUE(testing_master->WasFound(goog4)); |
| |
| // With the mock DNS, each of these should have taken some time, and hence |
| // shown a benefit (i.e., prefetch cost more than network access time). |
| |
| // Simulate actual navigation, and acrue the benefit for "helping" the DNS |
| // part of the navigation. |
| EXPECT_TRUE(testing_master->AccruePrefetchBenefits(GURL(), &goog_info)); |
| EXPECT_TRUE(testing_master->AccruePrefetchBenefits(GURL(), &goog2_info)); |
| EXPECT_TRUE(testing_master->AccruePrefetchBenefits(GURL(), &goog3_info)); |
| EXPECT_TRUE(testing_master->AccruePrefetchBenefits(GURL(), &goog4_info)); |
| |
| // Benefits can ONLY be reported once (for the first navigation). |
| EXPECT_FALSE(testing_master->AccruePrefetchBenefits(GURL(), &goog_info)); |
| EXPECT_FALSE(testing_master->AccruePrefetchBenefits(GURL(), &goog2_info)); |
| EXPECT_FALSE(testing_master->AccruePrefetchBenefits(GURL(), &goog3_info)); |
| EXPECT_FALSE(testing_master->AccruePrefetchBenefits(GURL(), &goog4_info)); |
| |
| testing_master->Shutdown(); |
| } |
| |
| TEST_F(DnsMasterTest, ShutdownWhenResolutionIsPendingTest) { |
| scoped_refptr<net::WaitingHostResolverProc> resolver_proc = |
| new net::WaitingHostResolverProc(NULL); |
| host_resolver_->Reset(resolver_proc); |
| |
| scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_, |
| default_max_queueing_delay_, |
| DnsGlobalInit::kMaxPrefetchConcurrentLookups); |
| |
| std::string localhost("127.0.0.1"); |
| NameList names; |
| names.insert(names.end(), localhost); |
| |
| testing_master->ResolveList(names, DnsHostInfo::PAGE_SCAN_MOTIVATED); |
| |
| MessageLoop::current()->PostDelayedTask(FROM_HERE, |
| new MessageLoop::QuitTask(), 500); |
| MessageLoop::current()->Run(); |
| |
| EXPECT_FALSE(testing_master->WasFound(localhost)); |
| |
| testing_master->Shutdown(); |
| |
| // Clean up after ourselves. |
| resolver_proc->Signal(); |
| MessageLoop::current()->RunAllPending(); |
| } |
| |
| TEST_F(DnsMasterTest, SingleLookupTest) { |
| scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_, |
| default_max_queueing_delay_, |
| DnsGlobalInit::kMaxPrefetchConcurrentLookups); |
| |
| std::string goog("www.google.com"); |
| |
| NameList names; |
| names.insert(names.end(), goog); |
| |
| // Try to flood the master with many concurrent requests. |
| for (int i = 0; i < 10; i++) |
| testing_master->ResolveList(names, DnsHostInfo::PAGE_SCAN_MOTIVATED); |
| |
| WaitForResolution(testing_master, names); |
| |
| EXPECT_TRUE(testing_master->WasFound(goog)); |
| |
| MessageLoop::current()->RunAllPending(); |
| |
| EXPECT_GT(testing_master->peak_pending_lookups(), names.size() / 2); |
| EXPECT_LE(testing_master->peak_pending_lookups(), names.size()); |
| EXPECT_LE(testing_master->peak_pending_lookups(), |
| testing_master->max_concurrent_lookups()); |
| |
| testing_master->Shutdown(); |
| } |
| |
| TEST_F(DnsMasterTest, ConcurrentLookupTest) { |
| host_resolver_->rules()->AddSimulatedFailure("*.notfound"); |
| |
| scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_, |
| default_max_queueing_delay_, |
| DnsGlobalInit::kMaxPrefetchConcurrentLookups); |
| |
| std::string goog("www.google.com"), |
| goog2("gmail.google.com.com"), |
| goog3("mail.google.com"), |
| goog4("gmail.com"); |
| std::string bad1("bad1.notfound"), |
| bad2("bad2.notfound"); |
| |
| NameList names; |
| names.insert(names.end(), goog); |
| names.insert(names.end(), goog3); |
| names.insert(names.end(), bad1); |
| names.insert(names.end(), goog2); |
| names.insert(names.end(), bad2); |
| names.insert(names.end(), goog4); |
| names.insert(names.end(), goog); |
| |
| // Try to flood the master with many concurrent requests. |
| for (int i = 0; i < 10; i++) |
| testing_master->ResolveList(names, DnsHostInfo::PAGE_SCAN_MOTIVATED); |
| |
| WaitForResolution(testing_master, names); |
| |
| EXPECT_TRUE(testing_master->WasFound(goog)); |
| EXPECT_TRUE(testing_master->WasFound(goog3)); |
| EXPECT_TRUE(testing_master->WasFound(goog2)); |
| EXPECT_TRUE(testing_master->WasFound(goog4)); |
| EXPECT_FALSE(testing_master->WasFound(bad1)); |
| EXPECT_FALSE(testing_master->WasFound(bad2)); |
| |
| MessageLoop::current()->RunAllPending(); |
| |
| EXPECT_FALSE(testing_master->WasFound(bad1)); |
| EXPECT_FALSE(testing_master->WasFound(bad2)); |
| |
| EXPECT_GT(testing_master->peak_pending_lookups(), names.size() / 2); |
| EXPECT_LE(testing_master->peak_pending_lookups(), names.size()); |
| EXPECT_LE(testing_master->peak_pending_lookups(), |
| testing_master->max_concurrent_lookups()); |
| |
| testing_master->Shutdown(); |
| } |
| |
| TEST_F(DnsMasterTest, MassiveConcurrentLookupTest) { |
| host_resolver_->rules()->AddSimulatedFailure("*.notfound"); |
| |
| scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_, |
| default_max_queueing_delay_, |
| DnsGlobalInit::kMaxPrefetchConcurrentLookups); |
| |
| NameList names; |
| for (int i = 0; i < 100; i++) |
| names.push_back("host" + IntToString(i) + ".notfound"); |
| |
| // Try to flood the master with many concurrent requests. |
| for (int i = 0; i < 10; i++) |
| testing_master->ResolveList(names, DnsHostInfo::PAGE_SCAN_MOTIVATED); |
| |
| WaitForResolution(testing_master, names); |
| |
| MessageLoop::current()->RunAllPending(); |
| |
| EXPECT_LE(testing_master->peak_pending_lookups(), names.size()); |
| EXPECT_LE(testing_master->peak_pending_lookups(), |
| testing_master->max_concurrent_lookups()); |
| |
| testing_master->Shutdown(); |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Functions to help synthesize and test serializations of subresource referrer |
| // lists. |
| |
| // Return a motivation_list if we can find one for the given motivating_host (or |
| // NULL if a match is not found). |
| static ListValue* FindSerializationMotivation(const std::string& motivation, |
| const ListValue& referral_list) { |
| ListValue* motivation_list(NULL); |
| for (size_t i = 0; i < referral_list.GetSize(); ++i) { |
| referral_list.GetList(i, &motivation_list); |
| std::string existing_motivation; |
| EXPECT_TRUE(motivation_list->GetString(i, &existing_motivation)); |
| if (existing_motivation == motivation) |
| break; |
| motivation_list = NULL; |
| } |
| return motivation_list; |
| } |
| |
| // Add a motivating_host and a subresource_host to a serialized list, using |
| // this given latency. This is a helper function for quickly building these |
| // lists. |
| static void AddToSerializedList(const std::string& motivation, |
| const std::string& subresource, int latency, |
| ListValue* referral_list ) { |
| // Find the motivation if it is already used. |
| ListValue* motivation_list = FindSerializationMotivation(motivation, |
| *referral_list); |
| if (!motivation_list) { |
| // This is the first mention of this motivation, so build a list. |
| motivation_list = new ListValue; |
| motivation_list->Append(new StringValue(motivation)); |
| // Provide empty subresource list. |
| motivation_list->Append(new ListValue()); |
| |
| // ...and make it part of the serialized referral_list. |
| referral_list->Append(motivation_list); |
| } |
| |
| ListValue* subresource_list(NULL); |
| EXPECT_TRUE(motivation_list->GetList(1, &subresource_list)); |
| |
| // We won't bother to check for the subresource being there already. Worst |
| // case, during deserialization, the latency value we supply plus the |
| // existing value(s) will be added to the referrer. |
| subresource_list->Append(new StringValue(subresource)); |
| subresource_list->Append(new FundamentalValue(latency)); |
| } |
| |
| static const int kLatencyNotFound = -1; |
| |
| // For a given motivation_hostname, and subresource_hostname, find what latency |
| // is currently listed. This assume a well formed serialization, which has |
| // at most one such entry for any pair of names. If no such pair is found, then |
| // return kLatencyNotFound. |
| int GetLatencyFromSerialization(const std::string& motivation, |
| const std::string& subresource, |
| const ListValue& referral_list) { |
| ListValue* motivation_list = FindSerializationMotivation(motivation, |
| referral_list); |
| if (!motivation_list) |
| return kLatencyNotFound; |
| ListValue* subresource_list; |
| EXPECT_TRUE(motivation_list->GetList(1, &subresource_list)); |
| for (size_t i = 0; i < subresource_list->GetSize(); ++i) { |
| std::string subresource_name; |
| EXPECT_TRUE(subresource_list->GetString(i, &subresource_name)); |
| if (subresource_name == subresource) { |
| int latency; |
| EXPECT_TRUE(subresource_list->GetInteger(i + 1, &latency)); |
| return latency; |
| } |
| ++i; // Skip latency value. |
| } |
| return kLatencyNotFound; |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| // Make sure nil referral lists really have no entries, and no latency listed. |
| TEST_F(DnsMasterTest, ReferrerSerializationNilTest) { |
| scoped_refptr<DnsMaster> master = new DnsMaster(host_resolver_, |
| default_max_queueing_delay_, |
| DnsGlobalInit::kMaxPrefetchConcurrentLookups); |
| ListValue referral_list; |
| master->SerializeReferrers(&referral_list); |
| EXPECT_EQ(0U, referral_list.GetSize()); |
| EXPECT_EQ(kLatencyNotFound, GetLatencyFromSerialization("a.com", "b.com", |
| referral_list)); |
| |
| master->Shutdown(); |
| } |
| |
| // Make sure that when a serialization list includes a value, that it can be |
| // deserialized into the database, and can be extracted back out via |
| // serialization without being changed. |
| TEST_F(DnsMasterTest, ReferrerSerializationSingleReferrerTest) { |
| scoped_refptr<DnsMaster> master = new DnsMaster(host_resolver_, |
| default_max_queueing_delay_, |
| DnsGlobalInit::kMaxPrefetchConcurrentLookups); |
| std::string motivation_hostname = "www.google.com"; |
| std::string subresource_hostname = "icons.google.com"; |
| const int kLatency = 3; |
| ListValue referral_list; |
| |
| AddToSerializedList(motivation_hostname, subresource_hostname, kLatency, |
| &referral_list); |
| |
| master->DeserializeReferrers(referral_list); |
| |
| ListValue recovered_referral_list; |
| master->SerializeReferrers(&recovered_referral_list); |
| EXPECT_EQ(1U, recovered_referral_list.GetSize()); |
| EXPECT_EQ(kLatency, GetLatencyFromSerialization(motivation_hostname, |
| subresource_hostname, |
| recovered_referral_list)); |
| |
| master->Shutdown(); |
| } |
| |
| // Make sure the Trim() functionality works as expected. |
| TEST_F(DnsMasterTest, ReferrerSerializationTrimTest) { |
| scoped_refptr<DnsMaster> master = new DnsMaster(host_resolver_, |
| default_max_queueing_delay_, |
| DnsGlobalInit::kMaxPrefetchConcurrentLookups); |
| std::string motivation_hostname = "www.google.com"; |
| std::string icon_subresource_hostname = "icons.google.com"; |
| std::string img_subresource_hostname = "img.google.com"; |
| ListValue referral_list; |
| |
| AddToSerializedList(motivation_hostname, icon_subresource_hostname, 10, |
| &referral_list); |
| AddToSerializedList(motivation_hostname, img_subresource_hostname, 3, |
| &referral_list); |
| |
| master->DeserializeReferrers(referral_list); |
| |
| ListValue recovered_referral_list; |
| master->SerializeReferrers(&recovered_referral_list); |
| EXPECT_EQ(1U, recovered_referral_list.GetSize()); |
| EXPECT_EQ(10, GetLatencyFromSerialization(motivation_hostname, |
| icon_subresource_hostname, |
| recovered_referral_list)); |
| EXPECT_EQ(3, GetLatencyFromSerialization(motivation_hostname, |
| img_subresource_hostname, |
| recovered_referral_list)); |
| |
| // Each time we Trim, the latency figures should reduce by a factor of two, |
| // until they both are 0, an then a trim will delete the whole entry. |
| master->TrimReferrers(); |
| master->SerializeReferrers(&recovered_referral_list); |
| EXPECT_EQ(1U, recovered_referral_list.GetSize()); |
| EXPECT_EQ(5, GetLatencyFromSerialization(motivation_hostname, |
| icon_subresource_hostname, |
| recovered_referral_list)); |
| EXPECT_EQ(1, GetLatencyFromSerialization(motivation_hostname, |
| img_subresource_hostname, |
| recovered_referral_list)); |
| |
| master->TrimReferrers(); |
| master->SerializeReferrers(&recovered_referral_list); |
| EXPECT_EQ(1U, recovered_referral_list.GetSize()); |
| EXPECT_EQ(2, GetLatencyFromSerialization(motivation_hostname, |
| icon_subresource_hostname, |
| recovered_referral_list)); |
| EXPECT_EQ(0, GetLatencyFromSerialization(motivation_hostname, |
| img_subresource_hostname, |
| recovered_referral_list)); |
| |
| master->TrimReferrers(); |
| master->SerializeReferrers(&recovered_referral_list); |
| EXPECT_EQ(1U, recovered_referral_list.GetSize()); |
| EXPECT_EQ(1, GetLatencyFromSerialization(motivation_hostname, |
| icon_subresource_hostname, |
| recovered_referral_list)); |
| EXPECT_EQ(0, GetLatencyFromSerialization(motivation_hostname, |
| img_subresource_hostname, |
| recovered_referral_list)); |
| |
| master->TrimReferrers(); |
| master->SerializeReferrers(&recovered_referral_list); |
| EXPECT_EQ(0U, recovered_referral_list.GetSize()); |
| EXPECT_EQ(kLatencyNotFound, |
| GetLatencyFromSerialization(motivation_hostname, |
| icon_subresource_hostname, |
| recovered_referral_list)); |
| EXPECT_EQ(kLatencyNotFound, |
| GetLatencyFromSerialization(motivation_hostname, |
| img_subresource_hostname, |
| recovered_referral_list)); |
| |
| master->Shutdown(); |
| } |
| |
| |
| TEST_F(DnsMasterTest, PriorityQueuePushPopTest) { |
| DnsMaster::HostNameQueue queue; |
| |
| // First check high priority queue FIFO functionality. |
| EXPECT_TRUE(queue.IsEmpty()); |
| queue.Push("a", DnsHostInfo::LEARNED_REFERAL_MOTIVATED); |
| EXPECT_FALSE(queue.IsEmpty()); |
| queue.Push("b", DnsHostInfo::MOUSE_OVER_MOTIVATED); |
| EXPECT_FALSE(queue.IsEmpty()); |
| EXPECT_EQ(queue.Pop(), "a"); |
| EXPECT_FALSE(queue.IsEmpty()); |
| EXPECT_EQ(queue.Pop(), "b"); |
| EXPECT_TRUE(queue.IsEmpty()); |
| |
| // Then check low priority queue FIFO functionality. |
| queue.Push("a", DnsHostInfo::PAGE_SCAN_MOTIVATED); |
| EXPECT_FALSE(queue.IsEmpty()); |
| queue.Push("b", DnsHostInfo::OMNIBOX_MOTIVATED); |
| EXPECT_FALSE(queue.IsEmpty()); |
| EXPECT_EQ(queue.Pop(), "a"); |
| EXPECT_FALSE(queue.IsEmpty()); |
| EXPECT_EQ(queue.Pop(), "b"); |
| EXPECT_TRUE(queue.IsEmpty()); |
| } |
| |
| TEST_F(DnsMasterTest, PriorityQueueReorderTest) { |
| DnsMaster::HostNameQueue queue; |
| |
| // Push all the low priority items. |
| EXPECT_TRUE(queue.IsEmpty()); |
| queue.Push("scan", DnsHostInfo::PAGE_SCAN_MOTIVATED); |
| queue.Push("unit", DnsHostInfo::UNIT_TEST_MOTIVATED); |
| queue.Push("lmax", DnsHostInfo::LINKED_MAX_MOTIVATED); |
| queue.Push("omni", DnsHostInfo::OMNIBOX_MOTIVATED); |
| queue.Push("startup", DnsHostInfo::STARTUP_LIST_MOTIVATED); |
| queue.Push("omni", DnsHostInfo::OMNIBOX_MOTIVATED); |
| |
| // Push all the high prority items |
| queue.Push("learned", DnsHostInfo::LEARNED_REFERAL_MOTIVATED); |
| queue.Push("refer", DnsHostInfo::STATIC_REFERAL_MOTIVATED); |
| queue.Push("mouse", DnsHostInfo::MOUSE_OVER_MOTIVATED); |
| |
| // Check that high priority stuff comes out first, and in FIFO order. |
| EXPECT_EQ(queue.Pop(), "learned"); |
| EXPECT_EQ(queue.Pop(), "refer"); |
| EXPECT_EQ(queue.Pop(), "mouse"); |
| |
| // ...and then low priority strings. |
| EXPECT_EQ(queue.Pop(), "scan"); |
| EXPECT_EQ(queue.Pop(), "unit"); |
| EXPECT_EQ(queue.Pop(), "lmax"); |
| EXPECT_EQ(queue.Pop(), "omni"); |
| EXPECT_EQ(queue.Pop(), "startup"); |
| EXPECT_EQ(queue.Pop(), "omni"); |
| |
| EXPECT_TRUE(queue.IsEmpty()); |
| } |
| |
| } // namespace chrome_browser_net |