| Index: content/browser/download/url_downloader_unittest.cc
|
| diff --git a/content/browser/download/url_downloader_unittest.cc b/content/browser/download/url_downloader_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..fc53237b9162867e11d05c93e2e9d82a581f9442
|
| --- /dev/null
|
| +++ b/content/browser/download/url_downloader_unittest.cc
|
| @@ -0,0 +1,397 @@
|
| +// 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 <vector>
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/memory/ref_counted.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "base/run_loop.h"
|
| +#include "base/strings/stringprintf.h"
|
| +#include "content/browser/byte_stream.h"
|
| +#include "content/browser/download/download_create_info.h"
|
| +#include "content/browser/download/download_interrupt_reasons_impl.h"
|
| +#include "content/browser/download/url_downloader.h"
|
| +#include "content/public/browser/browser_thread.h"
|
| +#include "content/public/browser/download_interrupt_reasons.h"
|
| +#include "content/public/browser/download_url_parameters.h"
|
| +#include "content/public/test/test_browser_context.h"
|
| +#include "content/public/test/test_browser_thread_bundle.h"
|
| +#include "net/base/net_errors.h"
|
| +#include "net/test/embedded_test_server/embedded_test_server.h"
|
| +#include "net/test/embedded_test_server/http_request.h"
|
| +#include "net/test/embedded_test_server/http_response.h"
|
| +#include "testing/gmock/include/gmock/gmock.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +using ::testing::AllOf;
|
| +using ::testing::Return;
|
| +using ::testing::_;
|
| +
|
| +namespace content {
|
| +namespace {
|
| +
|
| +class ByteStreamSlurper {
|
| + public:
|
| + typedef base::Callback<void(scoped_ptr<ByteStreamSlurper>)>
|
| + CompletionCallback;
|
| +
|
| + ByteStreamSlurper(scoped_ptr<ByteStreamReader> reader,
|
| + const CompletionCallback& callback);
|
| +
|
| + bool is_done() const { return is_done_; }
|
| + int state() const { return state_; }
|
| + const std::string& content() const { return content_; }
|
| +
|
| + void Start();
|
| +
|
| + private:
|
| + void Slurp();
|
| +
|
| + scoped_ptr<ByteStreamReader> reader_;
|
| + CompletionCallback callback_;
|
| + bool is_done_;
|
| + int state_;
|
| + std::string content_;
|
| +};
|
| +
|
| +ByteStreamSlurper::ByteStreamSlurper(scoped_ptr<ByteStreamReader> reader,
|
| + const CompletionCallback& callback)
|
| + : reader_(reader.Pass()),
|
| + callback_(callback),
|
| + is_done_(false),
|
| + state_(0) {
|
| +}
|
| +
|
| +void ByteStreamSlurper::Start() {
|
| + reader_->RegisterCallback(
|
| + base::Bind(&ByteStreamSlurper::Slurp,
|
| + base::Unretained(this)));
|
| + // Initial slurp.
|
| + Slurp();
|
| +}
|
| +
|
| +void ByteStreamSlurper::Slurp() {
|
| + while (!is_done_) {
|
| + scoped_refptr<net::IOBuffer> buffer;
|
| + size_t length = 0;
|
| + ByteStreamReader::StreamState state = reader_->Read(&buffer, &length);
|
| + switch (state) {
|
| + case ByteStreamReader::STREAM_EMPTY:
|
| + return; // We should get called when there's data available.
|
| +
|
| + case ByteStreamReader::STREAM_COMPLETE: {
|
| + DCHECK_EQ(0u, length);
|
| + is_done_ = true;
|
| + state_ = reader_->GetStatus();
|
| + scoped_ptr<ByteStreamSlurper> owned_this(this);
|
| + BrowserThread::PostTask(
|
| + BrowserThread::UI,
|
| + FROM_HERE,
|
| + base::Bind(callback_, base::Passed(&owned_this)));
|
| + break;
|
| + }
|
| +
|
| + case ByteStreamReader::STREAM_HAS_DATA:
|
| + content_.append(buffer->data(), length);
|
| + break;
|
| + }
|
| + }
|
| +}
|
| +
|
| +class Response : public net::test_server::HttpResponse {
|
| + public:
|
| + Response() : data_(new Data()) {}
|
| + Response(const Response& that) : data_(that.data_) {}
|
| + Response& Code(net::HttpStatusCode code) {
|
| + data_->response_.set_code(code);
|
| + return *this;
|
| + }
|
| + Response& Header(const char* name, const char* value) {
|
| + data_->response_.AddCustomHeader(name, value);
|
| + return *this;
|
| + }
|
| + Response& Content(const char* content) {
|
| + data_->response_.set_content(content);
|
| + return *this;
|
| + }
|
| + Response& ContentType(const char* content_type) {
|
| + data_->response_.set_content_type(content_type);
|
| + return *this;
|
| + }
|
| +
|
| + virtual std::string ToResponseString() const OVERRIDE {
|
| + return data_->response_.ToResponseString();
|
| + }
|
| +
|
| + private:
|
| + class Data : public base::RefCounted<Data> {
|
| + public:
|
| + Data() {}
|
| + net::test_server::BasicHttpResponse response_;
|
| +
|
| + private:
|
| + friend class base::RefCounted<Data>;
|
| + virtual ~Data() {}
|
| + };
|
| +
|
| + scoped_refptr<Data> data_;
|
| +};
|
| +
|
| +class RequestHandler {
|
| + public:
|
| + net::test_server::EmbeddedTestServer::HandleRequestCallback GetCallback() {
|
| + return base::Bind(&RequestHandler::HandleRequest, base::Unretained(this));
|
| + }
|
| +
|
| + MOCK_METHOD3(Request,
|
| + Response(const std::string&,
|
| + const std::map<std::string, std::string>&,
|
| + const std::string&));
|
| +
|
| + private:
|
| + scoped_ptr<net::test_server::HttpResponse> HandleRequest(
|
| + const net::test_server::HttpRequest& request);
|
| +};
|
| +
|
| +MATCHER_P2(HasHeader, name, value, "") {
|
| + return arg.find(name) != arg.end() && arg.find(name)->second == value;
|
| +}
|
| +
|
| +scoped_ptr<net::test_server::HttpResponse> RequestHandler::HandleRequest(
|
| + const net::test_server::HttpRequest& request) {
|
| + scoped_ptr<Response> response(new Response(
|
| + Request(request.relative_url, request.headers, request.content)));
|
| + return response.PassAs<net::test_server::HttpResponse>();
|
| +}
|
| +
|
| +class UrlDownloaderTest : public testing::Test {
|
| + public:
|
| + UrlDownloaderTest() {}
|
| + virtual ~UrlDownloaderTest() {}
|
| +
|
| + virtual void SetUp() OVERRIDE {
|
| + test_server_.reset(new net::test_server::EmbeddedTestServer(
|
| + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)));
|
| + test_server_->RegisterRequestHandler(handler_.GetCallback());
|
| + }
|
| +
|
| + virtual void TearDown() OVERRIDE {
|
| + ASSERT_TRUE(test_server_->ShutdownAndWaitUntilComplete());
|
| + base::RunLoop run_loop;
|
| + run_loop.RunUntilIdle();
|
| + }
|
| +
|
| + DownloadInterruptReason StartRequestAndWaitForCallback(
|
| + DownloadRequestHandle* request_handle,
|
| + scoped_ptr<DownloadCreateInfo>* info) {
|
| + base::RunLoop run_loop;
|
| + DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE;
|
| + request_handle->Start(base::Bind(&UrlDownloaderTest::StartRequestCallback,
|
| + &result,
|
| + info,
|
| + run_loop.QuitClosure()));
|
| + run_loop.Run();
|
| + return result;
|
| + }
|
| +
|
| + DownloadInterruptReason SlurpByteStream(
|
| + scoped_ptr<ByteStreamReader> stream_reader,
|
| + std::string* content) {
|
| + int status = 0;
|
| + base::RunLoop run_loop;
|
| + scoped_ptr<ByteStreamSlurper> slurper(
|
| + new ByteStreamSlurper(stream_reader.Pass(),
|
| + base::Bind(&UrlDownloaderTest::SlurpDoneCallback,
|
| + &status,
|
| + content,
|
| + run_loop.QuitClosure())));
|
| + BrowserThread::PostTask(BrowserThread::IO,
|
| + FROM_HERE,
|
| + base::Bind(&ByteStreamSlurper::Start,
|
| + base::Unretained(slurper.release())));
|
| + run_loop.Run();
|
| + return static_cast<DownloadInterruptReason>(status);
|
| + }
|
| +
|
| + TestBrowserContext* test_browser_context() { return &test_browser_context_; }
|
| +
|
| + RequestHandler* request_handler() { return &handler_; }
|
| +
|
| + net::test_server::EmbeddedTestServer* test_server() {
|
| + return test_server_.get();
|
| + }
|
| +
|
| + private:
|
| + static void StartRequestCallback(DownloadInterruptReason* store_result,
|
| + scoped_ptr<DownloadCreateInfo>* store_info,
|
| + const base::Closure& closure,
|
| + DownloadInterruptReason result,
|
| + scoped_ptr<DownloadCreateInfo> create_info) {
|
| + *store_result = result;
|
| + store_info->swap(create_info);
|
| + closure.Run();
|
| + }
|
| +
|
| + static void SlurpDoneCallback(int* status,
|
| + std::string* content,
|
| + const base::Closure& closure,
|
| + scoped_ptr<ByteStreamSlurper> slurper) {
|
| + *status = slurper->state();
|
| + *content = slurper->content();
|
| + BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, slurper.release());
|
| + closure.Run();
|
| + }
|
| +
|
| + TestBrowserThreadBundle thread_bundle_;
|
| + TestBrowserContext test_browser_context_;
|
| + scoped_ptr<net::test_server::EmbeddedTestServer> test_server_;
|
| + RequestHandler handler_;
|
| +};
|
| +
|
| +const char* const kDummyURL = "/foo";
|
| +const char* const kTextContentType = "text/plain";
|
| +const char* const kDummyContent = "Hello world";
|
| +
|
| +TEST_F(UrlDownloaderTest, UrlDownloader_Start) {
|
| + const char* kETag = "abcd";
|
| + ASSERT_TRUE(test_server()->InitializeAndWaitUntilReady());
|
| + GURL url = test_server()->GetURL(kDummyURL);
|
| + scoped_ptr<DownloadUrlParameters> params(new DownloadUrlParameters(
|
| + url, -1, -1, test_browser_context()->GetResourceContext()));
|
| + scoped_ptr<DownloadRequestHandle> request_handle(
|
| + UrlDownloader::CreateDownloadRequest(params.Pass()));
|
| +
|
| + EXPECT_CALL(*request_handler(), Request(kDummyURL, _, _))
|
| + .WillOnce(Return(Response()
|
| + .Code(net::HTTP_OK)
|
| + .Header("etag", kETag)
|
| + .ContentType(kTextContentType)
|
| + .Content(kDummyContent)));
|
| + scoped_ptr<DownloadCreateInfo> info;
|
| + DownloadInterruptReason result =
|
| + StartRequestAndWaitForCallback(request_handle.get(), &info);
|
| + ASSERT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, result);
|
| + ASSERT_TRUE(info);
|
| + EXPECT_STREQ(kETag, info->etag.c_str());
|
| + EXPECT_STREQ(kTextContentType, info->mime_type.c_str());
|
| +
|
| + std::string content;
|
| + ASSERT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE,
|
| + SlurpByteStream(info->stream_reader.Pass(), &content));
|
| + EXPECT_EQ(kDummyContent, content);
|
| +}
|
| +
|
| +TEST_F(UrlDownloaderTest, UrlDownloader_ValidRedirects) {
|
| + ASSERT_TRUE(test_server()->InitializeAndWaitUntilReady());
|
| + static const char* const kURL0 = "/foo";
|
| + static const char* const kURL1 = "/bar";
|
| + static const char* const kURL2 = "/baz";
|
| + std::vector<GURL> urls;
|
| + urls.push_back(test_server()->GetURL(kURL0));
|
| + urls.push_back(test_server()->GetURL(kURL1));
|
| + urls.push_back(test_server()->GetURL(kURL2));
|
| +
|
| + scoped_ptr<DownloadUrlParameters> params(
|
| + new DownloadUrlParameters(urls[0], -1, -1,
|
| + test_browser_context()->GetResourceContext()));
|
| + scoped_ptr<DownloadRequestHandle> request_handle(
|
| + UrlDownloader::CreateDownloadRequest(params.Pass()));
|
| +
|
| + EXPECT_CALL(*request_handler(), Request(kURL0, _, _))
|
| + .WillOnce(Return(Response()
|
| + .Code(net::HTTP_TEMPORARY_REDIRECT)
|
| + .Header("location", urls[1].spec().c_str())));
|
| + EXPECT_CALL(*request_handler(), Request(kURL1, _, _))
|
| + .WillOnce(Return(Response()
|
| + .Code(net::HTTP_TEMPORARY_REDIRECT)
|
| + .Header("location", urls[2].spec().c_str())));
|
| + EXPECT_CALL(*request_handler(), Request(kURL2, _, _))
|
| + .WillOnce(Return(Response()
|
| + .Code(net::HTTP_OK)
|
| + .ContentType(kTextContentType)
|
| + .Content(kDummyContent)));
|
| + scoped_ptr<DownloadCreateInfo> info;
|
| + DownloadInterruptReason result =
|
| + StartRequestAndWaitForCallback(request_handle.get(), &info);
|
| + ASSERT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, result);
|
| + ASSERT_TRUE(info);
|
| + EXPECT_EQ(urls, info->url_chain);
|
| +}
|
| +
|
| +TEST_F(UrlDownloaderTest, UrlDownloader_InvalidRedirects) {
|
| + ASSERT_TRUE(test_server()->InitializeAndWaitUntilReady());
|
| + static const char* const kURL0 = "/foo";
|
| + static const char* const kURL1 = "/bar";
|
| + static const char* const kURL2 = "notavalidscheme://baz";
|
| + std::vector<GURL> urls;
|
| + urls.push_back(test_server()->GetURL(kURL0));
|
| + urls.push_back(test_server()->GetURL(kURL1));
|
| + urls.push_back(GURL(kURL2));
|
| +
|
| + scoped_ptr<DownloadUrlParameters> params(
|
| + new DownloadUrlParameters(urls[0], -1, -1,
|
| + test_browser_context()->GetResourceContext()));
|
| + scoped_ptr<DownloadRequestHandle> request_handle(
|
| + UrlDownloader::CreateDownloadRequest(params.Pass()));
|
| +
|
| + EXPECT_CALL(*request_handler(), Request(kURL0, _, _))
|
| + .WillOnce(Return(Response()
|
| + .Code(net::HTTP_TEMPORARY_REDIRECT)
|
| + .Header("location", urls[1].spec().c_str())));
|
| + EXPECT_CALL(*request_handler(), Request(kURL1, _, _))
|
| + .WillOnce(Return(Response()
|
| + .Code(net::HTTP_TEMPORARY_REDIRECT)
|
| + .Header("location", urls[2].spec().c_str())));
|
| + scoped_ptr<DownloadCreateInfo> info;
|
| + DownloadInterruptReason result =
|
| + StartRequestAndWaitForCallback(request_handle.get(), &info);
|
| + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST, result);
|
| + EXPECT_FALSE(info);
|
| +}
|
| +
|
| +TEST_F(UrlDownloaderTest, UrlDownloader_404Handling) {
|
| + ASSERT_TRUE(test_server()->InitializeAndWaitUntilReady());
|
| + scoped_ptr<DownloadUrlParameters> params(
|
| + new DownloadUrlParameters(test_server()->GetURL(kDummyURL),
|
| + -1,
|
| + -1,
|
| + test_browser_context()->GetResourceContext()));
|
| + scoped_ptr<DownloadRequestHandle> request_handle(
|
| + UrlDownloader::CreateDownloadRequest(params.Pass()));
|
| + EXPECT_CALL(*request_handler(), Request(kDummyURL, _, _))
|
| + .WillOnce(Return(Response().Code(net::HTTP_NOT_FOUND)));
|
| + scoped_ptr<DownloadCreateInfo> info;
|
| + DownloadInterruptReason reason =
|
| + StartRequestAndWaitForCallback(request_handle.get(), &info);
|
| + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, reason);
|
| + EXPECT_TRUE(info);
|
| +
|
| + std::string content;
|
| + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT,
|
| + SlurpByteStream(info->stream_reader.Pass(), &content));
|
| + EXPECT_TRUE(content.empty());
|
| +}
|
| +
|
| +TEST_F(UrlDownloaderTest, UrlDownloader_InvalidScheme) {
|
| + GURL url("notarealscheme://foo/bar");
|
| + scoped_ptr<DownloadUrlParameters> params(new DownloadUrlParameters(
|
| + url, -1, -1, test_browser_context()->GetResourceContext()));
|
| + scoped_ptr<DownloadRequestHandle> request_handle(
|
| + UrlDownloader::CreateDownloadRequest(params.Pass()));
|
| + scoped_ptr<DownloadCreateInfo> info;
|
| + DownloadInterruptReason reason =
|
| + StartRequestAndWaitForCallback(request_handle.get(), &info);
|
| + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST, reason);
|
| + EXPECT_FALSE(info);
|
| +}
|
| +
|
| +// TODO(asanka): Test that back pressure is commuted correctly.
|
| +// TODO(asanka): Test that a successful response results in a
|
| +// DownloadCreateInfo and that the response data is available.
|
| +// TODO(asanka): Test that responses with errors are handled correctly.
|
| +// TODO(asanka): Test that request headers are set correctly.
|
| +// TODO(asanka): Test that pausing and resuming work correctly.
|
| +} // namespace
|
| +} // namespace content
|
|
|