| Index: components/cronet/stale_host_resolver_unittest.cc
 | 
| diff --git a/components/cronet/stale_host_resolver_unittest.cc b/components/cronet/stale_host_resolver_unittest.cc
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..cf3d6976952976ab9cff4f4caeafefcb07f5195e
 | 
| --- /dev/null
 | 
| +++ b/components/cronet/stale_host_resolver_unittest.cc
 | 
| @@ -0,0 +1,496 @@
 | 
| +// Copyright 2016 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 "components/cronet/stale_host_resolver.h"
 | 
| +
 | 
| +#include "base/callback_helpers.h"
 | 
| +#include "base/logging.h"
 | 
| +#include "base/memory/ptr_util.h"
 | 
| +#include "base/memory/ref_counted.h"
 | 
| +#include "base/message_loop/message_loop.h"
 | 
| +#include "base/run_loop.h"
 | 
| +#include "base/test/test_timeouts.h"
 | 
| +#include "base/values.h"
 | 
| +#include "components/cronet/url_request_context_config.h"
 | 
| +#include "net/base/net_errors.h"
 | 
| +#include "net/cert/cert_verifier.h"
 | 
| +#include "net/dns/host_resolver_proc.h"
 | 
| +#include "net/http/http_network_session.h"
 | 
| +#include "net/proxy/proxy_config.h"
 | 
| +#include "net/proxy/proxy_config_service_fixed.h"
 | 
| +#include "net/url_request/url_request_context.h"
 | 
| +#include "net/url_request/url_request_context_builder.h"
 | 
| +#include "testing/gtest/include/gtest/gtest.h"
 | 
| +
 | 
| +namespace cronet {
 | 
| +
 | 
| +namespace {
 | 
| +
 | 
| +const char kHostname[] = "example.com";
 | 
| +const char kCacheAddress[] = "1.1.1.1";
 | 
| +const char kNetworkAddress[] = "2.2.2.2";
 | 
| +const char kUninitializedAddress[] = "3.3.3.3";
 | 
| +const int kCacheEntryTTLSec = 300;
 | 
| +
 | 
| +const int kNoStaleDelaySec = 0;
 | 
| +const int kLongStaleDelaySec = 3600;
 | 
| +const uint16_t kPort = 12345;
 | 
| +
 | 
| +const int kAgeFreshSec = 0;
 | 
| +const int kAgeExpiredSec = kCacheEntryTTLSec * 2;
 | 
| +
 | 
| +// How long to wait for resolve calls to return. If the tests are working
 | 
| +// correctly, we won't end up waiting this long -- it's just a backup.
 | 
| +const int kWaitTimeoutSec = 1;
 | 
| +
 | 
| +net::AddressList MakeAddressList(const char* ip_address_str) {
 | 
| +  net::IPAddress address;
 | 
| +  bool rv = address.AssignFromIPLiteral(ip_address_str);
 | 
| +  DCHECK(rv);
 | 
| +
 | 
| +  net::AddressList address_list;
 | 
| +  address_list.push_back(net::IPEndPoint(address, 0u));
 | 
| +  return address_list;
 | 
| +}
 | 
| +
 | 
| +class MockHostResolverProc : public net::HostResolverProc {
 | 
| + public:
 | 
| +  MockHostResolverProc() : HostResolverProc(nullptr) {}
 | 
| +
 | 
| +  ~MockHostResolverProc() override {}
 | 
| +
 | 
| +  int Resolve(const std::string& hostname,
 | 
| +              net::AddressFamily address_family,
 | 
| +              net::HostResolverFlags host_resolver_flags,
 | 
| +              net::AddressList* address_list,
 | 
| +              int* os_error) override {
 | 
| +    *address_list = MakeAddressList(kNetworkAddress);
 | 
| +    return net::OK;
 | 
| +  }
 | 
| +};
 | 
| +
 | 
| +class StaleHostResolverTest : public testing::Test {
 | 
| + protected:
 | 
| +  StaleHostResolverTest()
 | 
| +      : mock_proc_(new MockHostResolverProc()),
 | 
| +        resolver_(nullptr),
 | 
| +        resolve_pending_(false),
 | 
| +        resolve_complete_(false) {}
 | 
| +
 | 
| +  ~StaleHostResolverTest() {}
 | 
| +
 | 
| +  void SetStaleDelay(int stale_delay_sec) {
 | 
| +    DCHECK(!resolver_);
 | 
| +
 | 
| +    options_.delay = base::TimeDelta::FromSeconds(stale_delay_sec);
 | 
| +  }
 | 
| +
 | 
| +  void SetStaleUsability(int max_expired_time_sec,
 | 
| +                         int max_stale_uses,
 | 
| +                         bool allow_other_network) {
 | 
| +    DCHECK(!resolver_);
 | 
| +
 | 
| +    options_.max_expired_time =
 | 
| +        base::TimeDelta::FromSeconds(max_expired_time_sec);
 | 
| +    options_.max_stale_uses = max_stale_uses;
 | 
| +    options_.allow_other_network = allow_other_network;
 | 
| +  }
 | 
| +
 | 
| +  void CreateResolver() {
 | 
| +    DCHECK(!resolver_);
 | 
| +
 | 
| +    std::unique_ptr<net::HostResolverImpl> inner_resolver(
 | 
| +        net::HostResolver::CreateDefaultResolverImpl(nullptr));
 | 
| +
 | 
| +    net::HostResolverImpl::ProcTaskParams proc_params(mock_proc_.get(), 1u);
 | 
| +    inner_resolver->set_proc_params_for_test(proc_params);
 | 
| +
 | 
| +    stale_resolver_ = base::WrapUnique(
 | 
| +        new StaleHostResolver(std::move(inner_resolver), options_));
 | 
| +    resolver_ = stale_resolver_.get();
 | 
| +  }
 | 
| +
 | 
| +  void DestroyResolver() {
 | 
| +    DCHECK(stale_resolver_);
 | 
| +
 | 
| +    stale_resolver_.reset();
 | 
| +    resolver_ = nullptr;
 | 
| +  }
 | 
| +
 | 
| +  void SetResolver(net::HostResolver* resolver) {
 | 
| +    DCHECK(!resolver_);
 | 
| +
 | 
| +    resolver_ = resolver;
 | 
| +  }
 | 
| +
 | 
| +  void ClearResolver() {
 | 
| +    DCHECK(resolver_);
 | 
| +    DCHECK(!stale_resolver_);
 | 
| +
 | 
| +    resolver_ = nullptr;
 | 
| +  }
 | 
| +
 | 
| +  // Creates a cache entry for |kHostname| that is |age_sec| seconds old.
 | 
| +  void CreateCacheEntry(int age_sec) {
 | 
| +    DCHECK(resolver_);
 | 
| +    DCHECK(resolver_->GetHostCache());
 | 
| +
 | 
| +    base::TimeDelta ttl(base::TimeDelta::FromSeconds(kCacheEntryTTLSec));
 | 
| +    net::HostCache::Key key(kHostname, net::ADDRESS_FAMILY_IPV4, 0);
 | 
| +    net::HostCache::Entry entry(net::OK, MakeAddressList(kCacheAddress), ttl);
 | 
| +    base::TimeDelta age = base::TimeDelta::FromSeconds(age_sec);
 | 
| +    base::TimeTicks then = base::TimeTicks::Now() - age;
 | 
| +    resolver_->GetHostCache()->Set(key, entry, then, ttl);
 | 
| +  }
 | 
| +
 | 
| +  void OnNetworkChange() {
 | 
| +    DCHECK(resolver_);
 | 
| +    DCHECK(resolver_->GetHostCache());
 | 
| +
 | 
| +    resolver_->GetHostCache()->OnNetworkChange();
 | 
| +  }
 | 
| +
 | 
| +  void LookupStale() {
 | 
| +    DCHECK(resolver_);
 | 
| +    DCHECK(resolver_->GetHostCache());
 | 
| +
 | 
| +    net::HostCache::Key key(kHostname, net::ADDRESS_FAMILY_IPV4, 0);
 | 
| +    base::TimeTicks now = base::TimeTicks::Now();
 | 
| +    const net::HostCache::Entry* entry;
 | 
| +    net::HostCache::EntryStaleness stale;
 | 
| +    entry = resolver_->GetHostCache()->LookupStale(key, now, &stale);
 | 
| +    EXPECT_TRUE(entry);
 | 
| +    EXPECT_TRUE(stale.is_stale());
 | 
| +  }
 | 
| +
 | 
| +  void Resolve() {
 | 
| +    DCHECK(resolver_);
 | 
| +    EXPECT_FALSE(resolve_pending_);
 | 
| +
 | 
| +    net::HostResolver::RequestInfo info(net::HostPortPair(kHostname, kPort));
 | 
| +    info.set_address_family(net::ADDRESS_FAMILY_IPV4);
 | 
| +
 | 
| +    resolve_pending_ = true;
 | 
| +    resolve_complete_ = false;
 | 
| +    resolve_addresses_ = MakeAddressList(kUninitializedAddress);
 | 
| +    resolve_error_ = net::ERR_UNEXPECTED;
 | 
| +
 | 
| +    int rv =
 | 
| +        resolver_->Resolve(info, net::DEFAULT_PRIORITY, &resolve_addresses_,
 | 
| +                           base::Bind(&StaleHostResolverTest::OnResolveComplete,
 | 
| +                                      base::Unretained(this)),
 | 
| +                           &request_, net::BoundNetLog());
 | 
| +    if (rv != net::ERR_IO_PENDING) {
 | 
| +      resolve_pending_ = false;
 | 
| +      resolve_complete_ = true;
 | 
| +      resolve_error_ = rv;
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  void WaitForResolve() {
 | 
| +    if (!resolve_pending_)
 | 
| +      return;
 | 
| +
 | 
| +    base::RunLoop run_loop;
 | 
| +
 | 
| +    // Run until resolve completes or timeout.
 | 
| +    resolve_closure_ = run_loop.QuitWhenIdleClosure();
 | 
| +    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
 | 
| +        FROM_HERE, run_loop.QuitWhenIdleClosure(),
 | 
| +        base::TimeDelta::FromSeconds(kWaitTimeoutSec));
 | 
| +    run_loop.Run();
 | 
| +  }
 | 
| +
 | 
| +  void WaitForIdle() {
 | 
| +    base::RunLoop run_loop;
 | 
| +
 | 
| +    base::ThreadTaskRunnerHandle::Get()->PostTask(
 | 
| +        FROM_HERE, run_loop.QuitWhenIdleClosure());
 | 
| +    run_loop.Run();
 | 
| +  }
 | 
| +
 | 
| +  void Cancel() {
 | 
| +    DCHECK(resolver_);
 | 
| +    EXPECT_TRUE(resolve_pending_);
 | 
| +
 | 
| +    delete request_.release();
 | 
| +
 | 
| +    resolve_pending_ = false;
 | 
| +  }
 | 
| +
 | 
| +  void OnResolveComplete(int error) {
 | 
| +    EXPECT_TRUE(resolve_pending_);
 | 
| +
 | 
| +    request_.reset();
 | 
| +
 | 
| +    resolve_error_ = error;
 | 
| +    resolve_pending_ = false;
 | 
| +    resolve_complete_ = true;
 | 
| +
 | 
| +    if (!resolve_closure_.is_null())
 | 
| +      base::ResetAndReturn(&resolve_closure_).Run();
 | 
| +  }
 | 
| +
 | 
| +  bool resolve_complete() const { return resolve_complete_; }
 | 
| +  int resolve_error() const { return resolve_error_; }
 | 
| +  const net::AddressList& resolve_addresses() const {
 | 
| +    return resolve_addresses_;
 | 
| +  }
 | 
| +
 | 
| + private:
 | 
| +  // Needed for HostResolver to run HostResolverProc callbacks.
 | 
| +  base::MessageLoopForIO message_loop_for_io_;
 | 
| +  scoped_refptr<MockHostResolverProc> mock_proc_;
 | 
| +
 | 
| +  net::HostResolver* resolver_;
 | 
| +  StaleHostResolver::StaleOptions options_;
 | 
| +  std::unique_ptr<StaleHostResolver> stale_resolver_;
 | 
| +
 | 
| +  base::TimeTicks now_;
 | 
| +  std::unique_ptr<net::HostResolver::Request> request_;
 | 
| +  bool resolve_pending_;
 | 
| +  bool resolve_complete_;
 | 
| +  net::AddressList resolve_addresses_;
 | 
| +  int resolve_error_;
 | 
| +
 | 
| +  base::Closure resolve_closure_;
 | 
| +};
 | 
| +
 | 
| +// Make sure that test harness can be created and destroyed without crashing.
 | 
| +TEST_F(StaleHostResolverTest, Null) {}
 | 
| +
 | 
| +// Make sure that resolver can be created and destroyed without crashing.
 | 
| +TEST_F(StaleHostResolverTest, Create) {
 | 
| +  CreateResolver();
 | 
| +}
 | 
| +
 | 
| +TEST_F(StaleHostResolverTest, Network) {
 | 
| +  CreateResolver();
 | 
| +
 | 
| +  Resolve();
 | 
| +  WaitForResolve();
 | 
| +
 | 
| +  EXPECT_TRUE(resolve_complete());
 | 
| +  EXPECT_EQ(net::OK, resolve_error());
 | 
| +  EXPECT_EQ(1u, resolve_addresses().size());
 | 
| +  EXPECT_EQ(kNetworkAddress, resolve_addresses()[0].ToStringWithoutPort());
 | 
| +}
 | 
| +
 | 
| +TEST_F(StaleHostResolverTest, FreshCache) {
 | 
| +  CreateResolver();
 | 
| +  CreateCacheEntry(kAgeFreshSec);
 | 
| +
 | 
| +  Resolve();
 | 
| +
 | 
| +  EXPECT_TRUE(resolve_complete());
 | 
| +  EXPECT_EQ(net::OK, resolve_error());
 | 
| +  EXPECT_EQ(1u, resolve_addresses().size());
 | 
| +  EXPECT_EQ(kCacheAddress, resolve_addresses()[0].ToStringWithoutPort());
 | 
| +
 | 
| +  WaitForIdle();
 | 
| +}
 | 
| +
 | 
| +TEST_F(StaleHostResolverTest, StaleCache) {
 | 
| +  SetStaleDelay(kNoStaleDelaySec);
 | 
| +  CreateResolver();
 | 
| +  CreateCacheEntry(kAgeExpiredSec);
 | 
| +
 | 
| +  Resolve();
 | 
| +  WaitForResolve();
 | 
| +
 | 
| +  EXPECT_TRUE(resolve_complete());
 | 
| +  EXPECT_EQ(net::OK, resolve_error());
 | 
| +  EXPECT_EQ(1u, resolve_addresses().size());
 | 
| +  EXPECT_EQ(kCacheAddress, resolve_addresses()[0].ToStringWithoutPort());
 | 
| +}
 | 
| +
 | 
| +TEST_F(StaleHostResolverTest, NetworkWithStaleCache) {
 | 
| +  SetStaleDelay(kLongStaleDelaySec);
 | 
| +  CreateResolver();
 | 
| +  CreateCacheEntry(kAgeExpiredSec);
 | 
| +
 | 
| +  Resolve();
 | 
| +  WaitForResolve();
 | 
| +
 | 
| +  EXPECT_TRUE(resolve_complete());
 | 
| +  EXPECT_EQ(net::OK, resolve_error());
 | 
| +  EXPECT_EQ(1u, resolve_addresses().size());
 | 
| +  EXPECT_EQ(kNetworkAddress, resolve_addresses()[0].ToStringWithoutPort());
 | 
| +}
 | 
| +
 | 
| +TEST_F(StaleHostResolverTest, CancelWithNoCache) {
 | 
| +  SetStaleDelay(kNoStaleDelaySec);
 | 
| +  CreateResolver();
 | 
| +
 | 
| +  Resolve();
 | 
| +
 | 
| +  Cancel();
 | 
| +
 | 
| +  EXPECT_FALSE(resolve_complete());
 | 
| +
 | 
| +  // Make sure there's no lingering |OnResolveComplete()| callback waiting.
 | 
| +  WaitForIdle();
 | 
| +}
 | 
| +
 | 
| +TEST_F(StaleHostResolverTest, CancelWithStaleCache) {
 | 
| +  SetStaleDelay(kLongStaleDelaySec);
 | 
| +  CreateResolver();
 | 
| +  CreateCacheEntry(kAgeExpiredSec);
 | 
| +
 | 
| +  Resolve();
 | 
| +
 | 
| +  Cancel();
 | 
| +
 | 
| +  EXPECT_FALSE(resolve_complete());
 | 
| +
 | 
| +  // Make sure there's no lingering |OnResolveComplete()| callback waiting.
 | 
| +  WaitForIdle();
 | 
| +}
 | 
| +
 | 
| +// CancelWithFreshCache makes no sense; the request would've returned
 | 
| +// synchronously.
 | 
| +
 | 
| +TEST_F(StaleHostResolverTest, StaleUsability) {
 | 
| +  const struct {
 | 
| +    int max_expired_time_sec;
 | 
| +    int max_stale_uses;
 | 
| +    bool allow_other_network;
 | 
| +
 | 
| +    int age_sec;
 | 
| +    int stale_use;
 | 
| +    int network_changes;
 | 
| +
 | 
| +    bool usable;
 | 
| +  } kUsabilityTestCases[] = {
 | 
| +      // Fresh data always accepted.
 | 
| +      {0, 0, true, -1, 1, 0, true},
 | 
| +      {1, 1, false, -1, 1, 0, true},
 | 
| +
 | 
| +      // Unlimited expired time accepts non-zero time.
 | 
| +      {0, 0, true, 1, 1, 0, true},
 | 
| +
 | 
| +      // Limited expired time accepts before but not after limit.
 | 
| +      {2, 0, true, 1, 1, 0, true},
 | 
| +      {2, 0, true, 3, 1, 0, false},
 | 
| +
 | 
| +      // Unlimited stale uses accepts first and later uses.
 | 
| +      {2, 0, true, 1, 1, 0, true},
 | 
| +      {2, 0, true, 1, 9, 0, true},
 | 
| +
 | 
| +      // Limited stale uses accepts up to and including limit.
 | 
| +      {2, 2, true, 1, 1, 0, true},
 | 
| +      {2, 2, true, 1, 2, 0, true},
 | 
| +      {2, 2, true, 1, 3, 0, false},
 | 
| +      {2, 2, true, 1, 9, 0, false},
 | 
| +
 | 
| +      // Allowing other networks accepts zero or more network changes.
 | 
| +      {2, 0, true, 1, 1, 0, true},
 | 
| +      {2, 0, true, 1, 1, 1, true},
 | 
| +      {2, 0, true, 1, 1, 9, true},
 | 
| +
 | 
| +      // Disallowing other networks only accepts zero network changes.
 | 
| +      {2, 0, false, 1, 1, 0, true},
 | 
| +      {2, 0, false, 1, 1, 1, false},
 | 
| +      {2, 0, false, 1, 1, 9, false},
 | 
| +  };
 | 
| +
 | 
| +  SetStaleDelay(kNoStaleDelaySec);
 | 
| +
 | 
| +  for (size_t i = 0; i < arraysize(kUsabilityTestCases); ++i) {
 | 
| +    const auto& test_case = kUsabilityTestCases[i];
 | 
| +
 | 
| +    SetStaleUsability(test_case.max_expired_time_sec, test_case.max_stale_uses,
 | 
| +                      test_case.allow_other_network);
 | 
| +    CreateResolver();
 | 
| +    CreateCacheEntry(kCacheEntryTTLSec + test_case.age_sec);
 | 
| +    for (int j = 0; j < test_case.network_changes; ++j)
 | 
| +      OnNetworkChange();
 | 
| +    for (int j = 0; j < test_case.stale_use - 1; ++j)
 | 
| +      LookupStale();
 | 
| +
 | 
| +    Resolve();
 | 
| +    WaitForResolve();
 | 
| +    EXPECT_TRUE(resolve_complete()) << i;
 | 
| +    EXPECT_EQ(net::OK, resolve_error()) << i;
 | 
| +    EXPECT_EQ(1u, resolve_addresses().size()) << i;
 | 
| +    {
 | 
| +      const char* expected = test_case.usable ? kCacheAddress : kNetworkAddress;
 | 
| +      EXPECT_EQ(expected, resolve_addresses()[0].ToStringWithoutPort()) << i;
 | 
| +    }
 | 
| +
 | 
| +    DestroyResolver();
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +TEST_F(StaleHostResolverTest, CreatedByContext) {
 | 
| +  URLRequestContextConfig config(
 | 
| +      // Enable QUIC.
 | 
| +      true,
 | 
| +      // QUIC User Agent ID.
 | 
| +      "Default QUIC User Agent ID",
 | 
| +      // Enable SPDY.
 | 
| +      true,
 | 
| +      // Enable SDCH.
 | 
| +      false,
 | 
| +      // Type of http cache.
 | 
| +      URLRequestContextConfig::HttpCacheType::DISK,
 | 
| +      // Max size of http cache in bytes.
 | 
| +      1024000,
 | 
| +      // Disable caching for HTTP responses. Other information may be stored in
 | 
| +      // the cache.
 | 
| +      false,
 | 
| +      // Storage path for http cache and cookie storage.
 | 
| +      "/data/data/org.chromium.net/app_cronet_test/test_storage",
 | 
| +      // User-Agent request header field.
 | 
| +      "fake agent",
 | 
| +      // JSON encoded experimental options.
 | 
| +      "{\"AsyncDNS\":{\"enable\":false},"
 | 
| +      "\"StaleDNS\":{\"enable\":true,"
 | 
| +      "\"delay_ms\":0,"
 | 
| +      "\"max_expired_time_ms\":0,"
 | 
| +      "\"max_stale_uses\":0}}",
 | 
| +      // Data reduction proxy key.
 | 
| +      "",
 | 
| +      // Data reduction proxy.
 | 
| +      "",
 | 
| +      // Fallback data reduction proxy.
 | 
| +      "",
 | 
| +      // Data reduction proxy secure proxy check URL.
 | 
| +      "",
 | 
| +      // MockCertVerifier to use for testing purposes.
 | 
| +      std::unique_ptr<net::CertVerifier>(),
 | 
| +      // Enable network quality estimator.
 | 
| +      false,
 | 
| +      // Enable Public Key Pinning bypass for local trust anchors.
 | 
| +      true,
 | 
| +      // Certificate verifier cache data.
 | 
| +      "");
 | 
| +
 | 
| +  net::URLRequestContextBuilder builder;
 | 
| +  net::NetLog net_log;
 | 
| +  config.ConfigureURLRequestContextBuilder(&builder, &net_log, nullptr);
 | 
| +  // Set a ProxyConfigService to avoid DCHECK failure when building.
 | 
| +  builder.set_proxy_config_service(base::WrapUnique(
 | 
| +      new net::ProxyConfigServiceFixed(net::ProxyConfig::CreateDirect())));
 | 
| +  std::unique_ptr<net::URLRequestContext> context(builder.Build());
 | 
| +
 | 
| +  // Duplicate StaleCache test case to ensure StaleHostResolver was created:
 | 
| +
 | 
| +  // Note: Experimental config above sets 0ms stale delay.
 | 
| +  SetResolver(context->host_resolver());
 | 
| +  CreateCacheEntry(kAgeExpiredSec);
 | 
| +
 | 
| +  Resolve();
 | 
| +  EXPECT_FALSE(resolve_complete());
 | 
| +  WaitForResolve();
 | 
| +
 | 
| +  EXPECT_TRUE(resolve_complete());
 | 
| +  EXPECT_EQ(net::OK, resolve_error());
 | 
| +  EXPECT_EQ(1u, resolve_addresses().size());
 | 
| +  EXPECT_EQ(kCacheAddress, resolve_addresses()[0].ToStringWithoutPort());
 | 
| +}
 | 
| +
 | 
| +}  // namespace
 | 
| +
 | 
| +}  // namespace cronet
 | 
| 
 |