| Index: net/url_request/sdch_dictionary_fetcher_unittest.cc
|
| diff --git a/net/url_request/sdch_dictionary_fetcher_unittest.cc b/net/url_request/sdch_dictionary_fetcher_unittest.cc
|
| index 1826ddb1681509bc7ffb8e47a84fbd59baa254d1..3d61ce362862754f9fdfcb27a3d1c2ab97ac8d6f 100644
|
| --- a/net/url_request/sdch_dictionary_fetcher_unittest.cc
|
| +++ b/net/url_request/sdch_dictionary_fetcher_unittest.cc
|
| @@ -5,13 +5,18 @@
|
| #include "net/url_request/sdch_dictionary_fetcher.h"
|
|
|
| #include <string>
|
| +#include <vector>
|
|
|
| #include "base/bind.h"
|
| #include "base/callback.h"
|
| +#include "base/logging.h"
|
| +#include "base/macros.h"
|
| #include "base/run_loop.h"
|
| #include "base/strings/stringprintf.h"
|
| #include "base/thread_task_runner_handle.h"
|
| +#include "net/base/load_flags.h"
|
| #include "net/base/sdch_manager.h"
|
| +#include "net/http/http_response_headers.h"
|
| #include "net/url_request/url_request_data_job.h"
|
| #include "net/url_request/url_request_filter.h"
|
| #include "net/url_request/url_request_interceptor.h"
|
| @@ -27,10 +32,17 @@ const char kTestDomain[] = "top.domain.test";
|
|
|
| class URLRequestSpecifiedResponseJob : public URLRequestSimpleJob {
|
| public:
|
| - URLRequestSpecifiedResponseJob(URLRequest* request,
|
| - NetworkDelegate* network_delegate,
|
| - const base::Closure& destruction_callback)
|
| + // Called on destruction with load flags used for this request.
|
| + typedef base::Callback<void(int)> DestructionCallback;
|
| +
|
| + URLRequestSpecifiedResponseJob(
|
| + URLRequest* request,
|
| + NetworkDelegate* network_delegate,
|
| + const HttpResponseInfo& response_info_to_return,
|
| + const DestructionCallback& destruction_callback)
|
| : URLRequestSimpleJob(request, network_delegate),
|
| + response_info_to_return_(response_info_to_return),
|
| + last_load_flags_seen_(request->load_flags()),
|
| destruction_callback_(destruction_callback) {
|
| DCHECK(!destruction_callback.is_null());
|
| }
|
| @@ -42,8 +54,15 @@ class URLRequestSpecifiedResponseJob : public URLRequestSimpleJob {
|
| url.spec().c_str());
|
| }
|
|
|
| + // URLRequestJob
|
| + void GetResponseInfo(HttpResponseInfo* info) override {
|
| + *info = response_info_to_return_;
|
| + }
|
| +
|
| private:
|
| - ~URLRequestSpecifiedResponseJob() override { destruction_callback_.Run(); }
|
| + ~URLRequestSpecifiedResponseJob() override {
|
| + destruction_callback_.Run(last_load_flags_seen_);
|
| + }
|
|
|
| // URLRequestSimpleJob implementation:
|
| int GetData(std::string* mime_type,
|
| @@ -55,19 +74,33 @@ class URLRequestSpecifiedResponseJob : public URLRequestSimpleJob {
|
| return OK;
|
| }
|
|
|
| - const base::Closure destruction_callback_;
|
| + const HttpResponseInfo response_info_to_return_;
|
| + int last_load_flags_seen_;
|
| + const DestructionCallback destruction_callback_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(URLRequestSpecifiedResponseJob);
|
| };
|
|
|
| class SpecifiedResponseJobInterceptor : public URLRequestInterceptor {
|
| public:
|
| // A callback to be called whenever a URLRequestSpecifiedResponseJob is
|
| - // created or destroyed. The argument will be the change in number of
|
| + // created or destroyed. The first argument will be the change in number of
|
| // jobs (i.e. +1 for created, -1 for destroyed).
|
| - typedef base::Callback<void(int outstanding_job_delta)> LifecycleCallback;
|
| -
|
| - explicit SpecifiedResponseJobInterceptor(
|
| - const LifecycleCallback& lifecycle_callback)
|
| - : lifecycle_callback_(lifecycle_callback), factory_(this) {
|
| + // The second argument will be undefined if the job is being created,
|
| + // and will contain the load flags passed to the request the
|
| + // job was created for if the job is being destroyed.
|
| + typedef base::Callback<void(int outstanding_job_delta,
|
| + int destruction_load_flags)> LifecycleCallback;
|
| +
|
| + // |*info| will be returned from all child URLRequestSpecifiedResponseJobs.
|
| + // Note that: a) this pointer is shared with the caller, and the caller must
|
| + // guarantee that |*info| outlives the SpecifiedResponseJobInterceptor, and
|
| + // b) |*info| is mutable, and changes to should propagate to
|
| + // URLRequestSpecifiedResponseJobs created after any change.
|
| + SpecifiedResponseJobInterceptor(HttpResponseInfo* http_response_info,
|
| + const LifecycleCallback& lifecycle_callback)
|
| + : http_response_info_(http_response_info),
|
| + lifecycle_callback_(lifecycle_callback) {
|
| DCHECK(!lifecycle_callback_.is_null());
|
| }
|
| ~SpecifiedResponseJobInterceptor() override {}
|
| @@ -75,19 +108,21 @@ class SpecifiedResponseJobInterceptor : public URLRequestInterceptor {
|
| URLRequestJob* MaybeInterceptRequest(
|
| URLRequest* request,
|
| NetworkDelegate* network_delegate) const override {
|
| - if (!lifecycle_callback_.is_null())
|
| - lifecycle_callback_.Run(1);
|
| + lifecycle_callback_.Run(1, 0);
|
|
|
| return new URLRequestSpecifiedResponseJob(
|
| - request, network_delegate, base::Bind(lifecycle_callback_, -1));
|
| + request, network_delegate, *http_response_info_,
|
| + base::Bind(lifecycle_callback_, -1));
|
| }
|
|
|
| - // The caller must ensure that the callback is valid to call for the
|
| - // lifetime of the SpecifiedResponseJobInterceptor (i.e. until
|
| - // Unregister() is called).
|
| - static void RegisterWithFilter(const LifecycleCallback& lifecycle_callback) {
|
| + // The caller must ensure that both |*http_response_info| and the
|
| + // callback remain valid for the lifetime of the
|
| + // SpecifiedResponseJobInterceptor (i.e. until Unregister() is called).
|
| + static void RegisterWithFilter(HttpResponseInfo* http_response_info,
|
| + const LifecycleCallback& lifecycle_callback) {
|
| scoped_ptr<SpecifiedResponseJobInterceptor> interceptor(
|
| - new SpecifiedResponseJobInterceptor(lifecycle_callback));
|
| + new SpecifiedResponseJobInterceptor(http_response_info,
|
| + lifecycle_callback));
|
|
|
| net::URLRequestFilter::GetInstance()->AddHostnameInterceptor(
|
| "http", kTestDomain, interceptor.Pass());
|
| @@ -99,26 +134,33 @@ class SpecifiedResponseJobInterceptor : public URLRequestInterceptor {
|
| }
|
|
|
| private:
|
| - void OnSpecfiedResponseJobDestruction() const {
|
| - if (!lifecycle_callback_.is_null())
|
| - lifecycle_callback_.Run(-1);
|
| - }
|
| -
|
| + HttpResponseInfo* http_response_info_;
|
| LifecycleCallback lifecycle_callback_;
|
| - mutable base::WeakPtrFactory<SpecifiedResponseJobInterceptor> factory_;
|
| + DISALLOW_COPY_AND_ASSIGN(SpecifiedResponseJobInterceptor);
|
| };
|
|
|
| // Local test infrastructure
|
| // * URLRequestSpecifiedResponseJob: A URLRequestJob that returns
|
| // a different but derivable response for each URL (used for all
|
| -// url requests in this file). The class provides interfaces to
|
| -// signal whenever the total number of jobs transitions to zero.
|
| -// * SdchDictionaryFetcherTest: Registers a callback with the above
|
| -// class, and provides blocking interfaces for a transition to zero jobs.
|
| -// Contains an SdchDictionaryFetcher, and tracks fetcher dictionary
|
| -// addition callbacks.
|
| -// Most tests schedule a dictionary fetch, wait for no jobs outstanding,
|
| -// then test that the fetch results are as expected.
|
| +// url requests in this file). This class is initialized with
|
| +// the HttpResponseInfo to return (if any), as well as a callback
|
| +// that is called when the class is destroyed. That callback
|
| +// takes as arguemnt the load flags used for the request the
|
| +// job was created for.
|
| +// * SpecifiedResponseJobInterceptor: This class is a
|
| +// URLRequestInterceptor that generates the class above. It is constructed
|
| +// with a pointer to the (mutable) resposne info that should be
|
| +// returned from the URLRequestSpecifiedResponseJob children, as well as
|
| +// a callback that is run when URLRequestSpecifiedResponseJobs are
|
| +// created or destroyed.
|
| +// * SdchDictionaryFetcherTest: This class registers the above interceptor,
|
| +// tracks the number of jobs requested and the subset of those
|
| +// that are still outstanding. It exports an interface to wait until there
|
| +// are no jobs outstanding. It shares an HttpResponseInfo structure
|
| +// with the SpecifiedResponseJobInterceptor to control the response
|
| +// information returned by the jbos.
|
| +// The standard pattern for tests is to schedule a dictionary fetch, wait
|
| +// for no jobs outstanding, then test that the fetch results are as expected.
|
|
|
| class SdchDictionaryFetcherTest : public ::testing::Test {
|
| public:
|
| @@ -134,13 +176,14 @@ class SdchDictionaryFetcherTest : public ::testing::Test {
|
| SdchDictionaryFetcherTest()
|
| : jobs_requested_(0),
|
| jobs_outstanding_(0),
|
| + last_load_flags_seen_(LOAD_NORMAL),
|
| context_(new TestURLRequestContext),
|
| - fetcher_(new SdchDictionaryFetcher(
|
| - context_.get(),
|
| - base::Bind(&SdchDictionaryFetcherTest::OnDictionaryFetched,
|
| - base::Unretained(this)))),
|
| + fetcher_(new SdchDictionaryFetcher(context_.get())),
|
| factory_(this) {
|
| + response_info_to_return_.request_time = base::Time::Now();
|
| + response_info_to_return_.response_time = base::Time::Now();
|
| SpecifiedResponseJobInterceptor::RegisterWithFilter(
|
| + &response_info_to_return_,
|
| base::Bind(&SdchDictionaryFetcherTest::OnNumberJobsChanged,
|
| factory_.GetWeakPtr()));
|
| }
|
| @@ -152,15 +195,15 @@ class SdchDictionaryFetcherTest : public ::testing::Test {
|
| void OnDictionaryFetched(const std::string& dictionary_text,
|
| const GURL& dictionary_url,
|
| const BoundNetLog& net_log) {
|
| - dictionary_additions.push_back(
|
| + dictionary_additions_.push_back(
|
| DictionaryAdditions(dictionary_text, dictionary_url));
|
| }
|
|
|
| // Return (in |*out|) all dictionary additions since the last time
|
| // this function was called.
|
| void GetDictionaryAdditions(std::vector<DictionaryAdditions>* out) {
|
| - out->swap(dictionary_additions);
|
| - dictionary_additions.clear();
|
| + out->swap(dictionary_additions_);
|
| + dictionary_additions_.clear();
|
| }
|
|
|
| SdchDictionaryFetcher* fetcher() { return fetcher_.get(); }
|
| @@ -168,7 +211,7 @@ class SdchDictionaryFetcherTest : public ::testing::Test {
|
| // May not be called outside the SetUp()/TearDown() interval.
|
| int jobs_requested() const { return jobs_requested_; }
|
|
|
| - GURL PathToGurl(const char* path) {
|
| + GURL PathToGurl(const char* path) const {
|
| std::string gurl_string("http://");
|
| gurl_string += kTestDomain;
|
| gurl_string += "/";
|
| @@ -186,10 +229,25 @@ class SdchDictionaryFetcherTest : public ::testing::Test {
|
| run_loop_.reset();
|
| }
|
|
|
| + HttpResponseInfo* response_info_to_return() {
|
| + return &response_info_to_return_;
|
| + }
|
| +
|
| + int last_load_flags_seen() const { return last_load_flags_seen_; }
|
| +
|
| + const SdchDictionaryFetcher::OnDictionaryFetchedCallback
|
| + GetDefaultCallback() {
|
| + return base::Bind(&SdchDictionaryFetcherTest::OnDictionaryFetched,
|
| + base::Unretained(this));
|
| + }
|
| +
|
| private:
|
| - void OnNumberJobsChanged(int outstanding_jobs_delta) {
|
| + void OnNumberJobsChanged(int outstanding_jobs_delta, int load_flags) {
|
| + DCHECK_NE(0, outstanding_jobs_delta);
|
| if (outstanding_jobs_delta > 0)
|
| jobs_requested_ += outstanding_jobs_delta;
|
| + else
|
| + last_load_flags_seen_ = load_flags;
|
| jobs_outstanding_ += outstanding_jobs_delta;
|
| if (jobs_outstanding_ == 0 && run_loop_)
|
| run_loop_->Quit();
|
| @@ -197,17 +255,32 @@ class SdchDictionaryFetcherTest : public ::testing::Test {
|
|
|
| int jobs_requested_;
|
| int jobs_outstanding_;
|
| +
|
| + // Last load flags seen by the interceptor installed in
|
| + // SdchDictionaryFetcherTest(). These are available to test bodies and
|
| + // currently used for ensuring that certain loads are marked only-from-cache.
|
| + int last_load_flags_seen_;
|
| +
|
| scoped_ptr<base::RunLoop> run_loop_;
|
| scoped_ptr<TestURLRequestContext> context_;
|
| scoped_ptr<SdchDictionaryFetcher> fetcher_;
|
| - std::vector<DictionaryAdditions> dictionary_additions;
|
| + std::vector<DictionaryAdditions> dictionary_additions_;
|
| +
|
| + // The request_time and response_time fields are filled in by the constructor
|
| + // for SdchDictionaryFetcherTest. Tests can fill the other fields of this
|
| + // member in to alter the HttpResponseInfo returned by the fetcher's
|
| + // URLRequestJob.
|
| + HttpResponseInfo response_info_to_return_;
|
| +
|
| base::WeakPtrFactory<SdchDictionaryFetcherTest> factory_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(SdchDictionaryFetcherTest);
|
| };
|
|
|
| // Schedule a fetch and make sure it happens.
|
| TEST_F(SdchDictionaryFetcherTest, Basic) {
|
| GURL dictionary_url(PathToGurl("dictionary"));
|
| - fetcher()->Schedule(dictionary_url);
|
| + fetcher()->Schedule(dictionary_url, GetDefaultCallback());
|
| WaitForNoJobs();
|
|
|
| EXPECT_EQ(1, jobs_requested());
|
| @@ -217,14 +290,15 @@ TEST_F(SdchDictionaryFetcherTest, Basic) {
|
| EXPECT_EQ(
|
| URLRequestSpecifiedResponseJob::ExpectedResponseForURL(dictionary_url),
|
| additions[0].dictionary_text);
|
| + EXPECT_FALSE(last_load_flags_seen() & LOAD_ONLY_FROM_CACHE);
|
| }
|
|
|
| // Multiple fetches of the same URL should result in only one request.
|
| TEST_F(SdchDictionaryFetcherTest, Multiple) {
|
| GURL dictionary_url(PathToGurl("dictionary"));
|
| - fetcher()->Schedule(dictionary_url);
|
| - fetcher()->Schedule(dictionary_url);
|
| - fetcher()->Schedule(dictionary_url);
|
| + EXPECT_TRUE(fetcher()->Schedule(dictionary_url, GetDefaultCallback()));
|
| + EXPECT_FALSE(fetcher()->Schedule(dictionary_url, GetDefaultCallback()));
|
| + EXPECT_FALSE(fetcher()->Schedule(dictionary_url, GetDefaultCallback()));
|
| WaitForNoJobs();
|
|
|
| EXPECT_EQ(1, jobs_requested());
|
| @@ -242,9 +316,9 @@ TEST_F(SdchDictionaryFetcherTest, Cancel) {
|
| GURL dictionary_url_2(PathToGurl("dictionary_2"));
|
| GURL dictionary_url_3(PathToGurl("dictionary_3"));
|
|
|
| - fetcher()->Schedule(dictionary_url_1);
|
| - fetcher()->Schedule(dictionary_url_2);
|
| - fetcher()->Schedule(dictionary_url_3);
|
| + fetcher()->Schedule(dictionary_url_1, GetDefaultCallback());
|
| + fetcher()->Schedule(dictionary_url_2, GetDefaultCallback());
|
| + fetcher()->Schedule(dictionary_url_3, GetDefaultCallback());
|
| fetcher()->Cancel();
|
| WaitForNoJobs();
|
|
|
| @@ -257,8 +331,8 @@ TEST_F(SdchDictionaryFetcherTest, Cancel) {
|
| TEST_F(SdchDictionaryFetcherTest, LoopRace) {
|
| GURL dictionary0_url(PathToGurl("dictionary0"));
|
| GURL dictionary1_url(PathToGurl("dictionary1"));
|
| - fetcher()->Schedule(dictionary0_url);
|
| - fetcher()->Schedule(dictionary1_url);
|
| + fetcher()->Schedule(dictionary0_url, GetDefaultCallback());
|
| + fetcher()->Schedule(dictionary1_url, GetDefaultCallback());
|
| WaitForNoJobs();
|
|
|
| ASSERT_EQ(2, jobs_requested());
|
| @@ -273,6 +347,90 @@ TEST_F(SdchDictionaryFetcherTest, LoopRace) {
|
| additions[1].dictionary_text);
|
| }
|
|
|
| +TEST_F(SdchDictionaryFetcherTest, ScheduleReloadLoadFlags) {
|
| + GURL dictionary_url(PathToGurl("dictionary"));
|
| + fetcher()->ScheduleReload(dictionary_url, GetDefaultCallback());
|
| +
|
| + WaitForNoJobs();
|
| + EXPECT_EQ(1, jobs_requested());
|
| + std::vector<DictionaryAdditions> additions;
|
| + GetDictionaryAdditions(&additions);
|
| + ASSERT_EQ(1u, additions.size());
|
| + EXPECT_EQ(
|
| + URLRequestSpecifiedResponseJob::ExpectedResponseForURL(dictionary_url),
|
| + additions[0].dictionary_text);
|
| + EXPECT_TRUE(last_load_flags_seen() & LOAD_ONLY_FROM_CACHE);
|
| +}
|
| +
|
| +TEST_F(SdchDictionaryFetcherTest, ScheduleReloadFresh) {
|
| + std::string raw_headers = "\0";
|
| + response_info_to_return()->headers = new HttpResponseHeaders(
|
| + HttpUtil::AssembleRawHeaders(raw_headers.data(), raw_headers.size()));
|
| + response_info_to_return()->headers->AddHeader("Cache-Control: max-age=1000");
|
| +
|
| + GURL dictionary_url(PathToGurl("dictionary"));
|
| + fetcher()->ScheduleReload(dictionary_url, GetDefaultCallback());
|
| +
|
| + WaitForNoJobs();
|
| + EXPECT_EQ(1, jobs_requested());
|
| + std::vector<DictionaryAdditions> additions;
|
| + GetDictionaryAdditions(&additions);
|
| + ASSERT_EQ(1u, additions.size());
|
| + EXPECT_EQ(
|
| + URLRequestSpecifiedResponseJob::ExpectedResponseForURL(dictionary_url),
|
| + additions[0].dictionary_text);
|
| + EXPECT_TRUE(last_load_flags_seen() & LOAD_ONLY_FROM_CACHE);
|
| +}
|
| +
|
| +TEST_F(SdchDictionaryFetcherTest, ScheduleReloadStale) {
|
| + response_info_to_return()->headers = new HttpResponseHeaders("");
|
| + response_info_to_return()->headers->AddHeader("Cache-Control: no-cache");
|
| +
|
| + GURL dictionary_url(PathToGurl("dictionary"));
|
| + fetcher()->ScheduleReload(dictionary_url, GetDefaultCallback());
|
| +
|
| + WaitForNoJobs();
|
| + EXPECT_EQ(1, jobs_requested());
|
| + std::vector<DictionaryAdditions> additions;
|
| + GetDictionaryAdditions(&additions);
|
| + EXPECT_EQ(0u, additions.size());
|
| + EXPECT_TRUE(last_load_flags_seen() & LOAD_ONLY_FROM_CACHE);
|
| +}
|
| +
|
| +TEST_F(SdchDictionaryFetcherTest, ScheduleReloadThenLoad) {
|
| + GURL dictionary_url(PathToGurl("dictionary"));
|
| + EXPECT_TRUE(fetcher()->ScheduleReload(dictionary_url, GetDefaultCallback()));
|
| + EXPECT_TRUE(fetcher()->Schedule(dictionary_url, GetDefaultCallback()));
|
| +
|
| + WaitForNoJobs();
|
| + EXPECT_EQ(2, jobs_requested());
|
| +}
|
| +
|
| +TEST_F(SdchDictionaryFetcherTest, ScheduleLoadThenReload) {
|
| + GURL dictionary_url(PathToGurl("dictionary"));
|
| + EXPECT_TRUE(fetcher()->Schedule(dictionary_url, GetDefaultCallback()));
|
| + EXPECT_FALSE(fetcher()->ScheduleReload(dictionary_url, GetDefaultCallback()));
|
| +
|
| + WaitForNoJobs();
|
| + EXPECT_EQ(1, jobs_requested());
|
| +}
|
| +
|
| +TEST_F(SdchDictionaryFetcherTest, CancelAllowsFutureFetches) {
|
| + GURL dictionary_url(PathToGurl("dictionary"));
|
| + EXPECT_TRUE(fetcher()->Schedule(dictionary_url, GetDefaultCallback()));
|
| + EXPECT_FALSE(fetcher()->Schedule(dictionary_url, GetDefaultCallback()));
|
| +
|
| + WaitForNoJobs();
|
| + EXPECT_EQ(1, jobs_requested());
|
| +
|
| + fetcher()->Cancel();
|
| + WaitForNoJobs();
|
| + EXPECT_TRUE(fetcher()->Schedule(dictionary_url, GetDefaultCallback()));
|
| +
|
| + WaitForNoJobs();
|
| + EXPECT_EQ(2, jobs_requested());
|
| +}
|
| +
|
| } // namespace
|
|
|
| } // namespace net
|
|
|