blob: 2f83d305be56fd7982980842361629667c5fd168 [file] [log] [blame]
// 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