普通文本  |  359行  |  13.12 KB

// Copyright 2013 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/precache/content/precache_manager.h"

#include <list>
#include <map>
#include <set>
#include <string>

#include "base/basictypes.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_samples.h"
#include "base/metrics/statistics_recorder.h"
#include "components/precache/core/precache_switches.h"
#include "components/precache/core/url_list_provider.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/http/http_status_code.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_request_status.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace precache {

namespace {

// A map of histogram names to the total sample counts.
typedef std::map<std::string, base::HistogramBase::Count> HistogramCountMap;

const char kConfigURL[] = "http://config-url.com";
const char kManifestURLPrefix[] = "http://manifest-url-prefix.com/";

base::HistogramBase::Count GetHistogramTotalCount(const char* histogram_name) {
  base::HistogramBase* histogram =
      base::StatisticsRecorder::FindHistogram(histogram_name);
  return histogram ? histogram->SnapshotSamples()->TotalCount() : 0;
}

HistogramCountMap GetHistogramCountMap() {
  // Note that the PrecacheManager tests don't care about the ".Cellular"
  // histograms.
  const char* kHistogramNames[] = {"Precache.DownloadedPrecacheMotivated",
                                   "Precache.DownloadedNonPrecache",
                                   "Precache.Saved"};

  HistogramCountMap histogram_count_map;
  for (size_t i = 0; i < arraysize(kHistogramNames); ++i) {
    histogram_count_map[kHistogramNames[i]] =
        GetHistogramTotalCount(kHistogramNames[i]);
  }
  return histogram_count_map;
}

class TestURLFetcherCallback {
 public:
  scoped_ptr<net::FakeURLFetcher> CreateURLFetcher(
      const GURL& url, net::URLFetcherDelegate* delegate,
      const std::string& response_data, net::HttpStatusCode response_code,
      net::URLRequestStatus::Status status) {
    scoped_ptr<net::FakeURLFetcher> fetcher(new net::FakeURLFetcher(
        url, delegate, response_data, response_code, status));

    requested_urls_.insert(url);
    return fetcher.Pass();
  }

  const std::multiset<GURL>& requested_urls() const {
    return requested_urls_;
  }

 private:
  // Multiset with one entry for each URL requested.
  std::multiset<GURL> requested_urls_;
};

class FakeURLListProvider : public URLListProvider {
 public:
  FakeURLListProvider(const std::list<GURL>& urls, bool run_immediately)
      : urls_(urls),
        run_immediately_(run_immediately),
        was_get_urls_called_(false) {}

  virtual void GetURLs(const GetURLsCallback& callback) OVERRIDE {
    was_get_urls_called_ = true;

    if (run_immediately_) {
      callback.Run(urls_);
    } else {
      // Post the callback to be run later in the message loop.
      base::MessageLoop::current()->PostTask(FROM_HERE,
                                             base::Bind(callback, urls_));
    }
  }

  bool was_get_urls_called() const {
    return was_get_urls_called_;
  }

 private:
  const std::list<GURL> urls_;
  const bool run_immediately_;
  bool was_get_urls_called_;
};

class TestPrecacheCompletionCallback {
 public:
  TestPrecacheCompletionCallback() : was_on_done_called_(false) {}

  void OnDone() {
    was_on_done_called_ = true;
  }

  PrecacheManager::PrecacheCompletionCallback GetCallback() {
    return base::Bind(&TestPrecacheCompletionCallback::OnDone,
                      base::Unretained(this));
  }

  bool was_on_done_called() const {
    return was_on_done_called_;
  }

 private:
  bool was_on_done_called_;
};

class PrecacheManagerTest : public testing::Test {
 public:
  PrecacheManagerTest()
      : precache_manager_(&browser_context_),
        factory_(NULL, base::Bind(&TestURLFetcherCallback::CreateURLFetcher,
                                  base::Unretained(&url_callback_))) {}

 protected:
  virtual void SetUp() OVERRIDE {
    base::StatisticsRecorder::Initialize();

    CommandLine::ForCurrentProcess()->AppendSwitchASCII(
        switches::kPrecacheConfigSettingsURL, kConfigURL);
    CommandLine::ForCurrentProcess()->AppendSwitchASCII(
        switches::kPrecacheManifestURLPrefix, kManifestURLPrefix);

    // Make the fetch of the precache configuration settings fail. Precaching
    // should still complete normally in this case.
    factory_.SetFakeResponse(GURL(kConfigURL), "",
                             net::HTTP_INTERNAL_SERVER_ERROR,
                             net::URLRequestStatus::FAILED);
  }

  content::TestBrowserThreadBundle test_browser_thread_bundle_;
  content::TestBrowserContext browser_context_;
  PrecacheManager precache_manager_;
  TestURLFetcherCallback url_callback_;
  net::FakeURLFetcherFactory factory_;
  TestPrecacheCompletionCallback precache_callback_;
};

TEST_F(PrecacheManagerTest, StartAndFinishPrecaching) {
  EXPECT_FALSE(precache_manager_.IsPrecaching());

  FakeURLListProvider url_list_provider(
      std::list<GURL>(1, GURL("http://starting-url.com")), false);
  precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
                                    &url_list_provider);

  EXPECT_TRUE(precache_manager_.IsPrecaching());

  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_FALSE(precache_manager_.IsPrecaching());
  EXPECT_TRUE(url_list_provider.was_get_urls_called());
  EXPECT_TRUE(precache_callback_.was_on_done_called());

  std::multiset<GURL> expected_requested_urls;
  expected_requested_urls.insert(GURL(kConfigURL));
  EXPECT_EQ(expected_requested_urls, url_callback_.requested_urls());
}

TEST_F(PrecacheManagerTest, StartAndCancelPrecachingBeforeURLsReceived) {
  EXPECT_FALSE(precache_manager_.IsPrecaching());

  FakeURLListProvider url_list_provider(
      std::list<GURL>(1, GURL("http://starting-url.com")), false);

  precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
                                    &url_list_provider);
  EXPECT_TRUE(precache_manager_.IsPrecaching());

  precache_manager_.CancelPrecaching();
  EXPECT_FALSE(precache_manager_.IsPrecaching());

  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_FALSE(precache_manager_.IsPrecaching());
  EXPECT_TRUE(url_list_provider.was_get_urls_called());
  EXPECT_FALSE(precache_callback_.was_on_done_called());
  EXPECT_TRUE(url_callback_.requested_urls().empty());
}

TEST_F(PrecacheManagerTest, StartAndCancelPrecachingAfterURLsReceived) {
  EXPECT_FALSE(precache_manager_.IsPrecaching());

  FakeURLListProvider url_list_provider(
      std::list<GURL>(1, GURL("http://starting-url.com")), true);

  precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
                                    &url_list_provider);

  // Since the |url_list_provider| ran the callback immediately, Start() has
  // been called on the PrecacheFetcher, and the precache config settings have
  // been requested. The response has not yet been received though, so
  // precaching is still in progress.
  EXPECT_TRUE(precache_manager_.IsPrecaching());

  precache_manager_.CancelPrecaching();
  EXPECT_FALSE(precache_manager_.IsPrecaching());

  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_FALSE(precache_manager_.IsPrecaching());
  EXPECT_TRUE(url_list_provider.was_get_urls_called());
  EXPECT_FALSE(precache_callback_.was_on_done_called());

  // Even though the response for the precache config settings should not have
  // been received, the request should still have been made.
  std::multiset<GURL> expected_requested_urls;
  expected_requested_urls.insert(GURL(kConfigURL));
  EXPECT_EQ(expected_requested_urls, url_callback_.requested_urls());
}

TEST_F(PrecacheManagerTest, RecordStatsForFetchWithIrrelevantFetches) {
  HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();

  // Fetches with size 0 should be ignored.
  precache_manager_.RecordStatsForFetch(GURL("http://url.com"), base::Time(), 0,
                                        false);
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());

  // Fetches for URLs with schemes other than HTTP or HTTPS should be ignored.
  precache_manager_.RecordStatsForFetch(GURL("ftp://ftp.com"), base::Time(),
                                        1000, false);
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());

  // Fetches for empty URLs should be ignored.
  precache_manager_.RecordStatsForFetch(GURL(), base::Time(), 1000, false);
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
}

TEST_F(PrecacheManagerTest, RecordStatsForFetchDuringPrecaching) {
  HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();

  FakeURLListProvider url_list_provider(std::list<GURL>(), false);
  precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
                                    &url_list_provider);

  EXPECT_TRUE(precache_manager_.IsPrecaching());
  precache_manager_.RecordStatsForFetch(GURL("http://url.com"), base::Time(),
                                        1000, false);

  precache_manager_.CancelPrecaching();

  base::MessageLoop::current()->RunUntilIdle();
  expected_histogram_count_map["Precache.DownloadedPrecacheMotivated"]++;
  EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
}

TEST_F(PrecacheManagerTest, RecordStatsForFetchHTTP) {
  HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();

  precache_manager_.RecordStatsForFetch(GURL("http://http-url.com"),
                                        base::Time(), 1000, false);
  base::MessageLoop::current()->RunUntilIdle();

  expected_histogram_count_map["Precache.DownloadedNonPrecache"]++;
  EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
}

TEST_F(PrecacheManagerTest, RecordStatsForFetchHTTPS) {
  HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();

  precache_manager_.RecordStatsForFetch(GURL("https://https-url.com"),
                                        base::Time(), 1000, false);
  base::MessageLoop::current()->RunUntilIdle();

  expected_histogram_count_map["Precache.DownloadedNonPrecache"]++;
  EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
}

TEST_F(PrecacheManagerTest, DeleteExpiredPrecacheHistory) {
  // This test has to use Time::Now() because StartPrecaching uses Time::Now().
  const base::Time kCurrentTime = base::Time::Now();
  HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();

  FakeURLListProvider url_list_provider(std::list<GURL>(), false);
  precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
                                    &url_list_provider);
  EXPECT_TRUE(precache_manager_.IsPrecaching());

  // Precache a bunch of URLs, with different fetch times.
  precache_manager_.RecordStatsForFetch(
      GURL("http://old-fetch.com"),
      kCurrentTime - base::TimeDelta::FromDays(61), 1000, false);
  precache_manager_.RecordStatsForFetch(
      GURL("http://recent-fetch.com"),
      kCurrentTime - base::TimeDelta::FromDays(59), 1000, false);
  precache_manager_.RecordStatsForFetch(
      GURL("http://yesterday-fetch.com"),
      kCurrentTime - base::TimeDelta::FromDays(1), 1000, false);
  expected_histogram_count_map["Precache.DownloadedPrecacheMotivated"] += 3;

  precache_manager_.CancelPrecaching();
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());

  // The expired precache will be deleted during precaching this time.
  precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
                                    &url_list_provider);
  EXPECT_TRUE(precache_manager_.IsPrecaching());

  precache_manager_.CancelPrecaching();
  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_FALSE(precache_manager_.IsPrecaching());

  // A fetch for the same URL as the expired precache was served from the cache,
  // but it isn't reported as saved bytes because it had expired in the precache
  // history.
  precache_manager_.RecordStatsForFetch(
      GURL("http://old-fetch.com"),
      kCurrentTime, 1000, true);

  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());

  // The other precaches should not have expired, so the following fetches from
  // the cache should count as saved bytes.
  precache_manager_.RecordStatsForFetch(
      GURL("http://recent-fetch.com"),
      kCurrentTime, 1000, true);
  precache_manager_.RecordStatsForFetch(
      GURL("http://yesterday-fetch.com"),
      kCurrentTime, 1000, true);
  expected_histogram_count_map["Precache.Saved"] += 2;

  base::MessageLoop::current()->RunUntilIdle();
  EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
}

}  // namespace

}  // namespace precache