| // Copyright 2017 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/download/internal/background_service/in_memory_download.h" |
| |
| #include "base/guid.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/threading/thread.h" |
| #include "net/base/io_buffer.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "storage/browser/blob/blob_reader.h" |
| #include "storage/browser/blob/blob_storage_context.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using testing::_; |
| using testing::NiceMock; |
| |
| namespace download { |
| namespace { |
| |
| const char kTestDownloadData[] = |
| "In earlier tellings, the dog had a better reputation than the cat, " |
| "however the president veto it."; |
| |
| MATCHER_P2(InMemoryDownloadMatcher, |
| response_headers, |
| url_chain, |
| "Verify in memory download.") { |
| return arg->response_headers()->raw_headers() == response_headers && |
| arg->url_chain() == url_chain; |
| } |
| |
| // Dummy callback used for IO_PENDING state in blob operations, this is not |
| // called when the blob operation is done, but called when chained with other |
| // IO operations that might return IO_PENDING. |
| template <typename T> |
| void SetValue(T* address, T value) { |
| *address = value; |
| } |
| |
| class MockDelegate : public InMemoryDownload::Delegate { |
| public: |
| MockDelegate() = default; |
| |
| void WaitForCompletion() { |
| DCHECK(!run_loop_.running()); |
| run_loop_.Run(); |
| } |
| |
| // InMemoryDownload::Delegate implementation. |
| MOCK_METHOD1(OnDownloadProgress, void(InMemoryDownload*)); |
| MOCK_METHOD1(OnDownloadStarted, void(InMemoryDownload*)); |
| void OnDownloadComplete(InMemoryDownload* download) override { |
| if (run_loop_.running()) |
| run_loop_.Quit(); |
| } |
| |
| private: |
| base::RunLoop run_loop_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockDelegate); |
| }; |
| |
| // Must run on IO thread task runner. |
| base::WeakPtr<storage::BlobStorageContext> BlobStorageContextGetter( |
| storage::BlobStorageContext* blob_context) { |
| DCHECK(blob_context); |
| return blob_context->AsWeakPtr(); |
| } |
| |
| class InMemoryDownloadTest : public testing::Test { |
| public: |
| InMemoryDownloadTest() = default; |
| ~InMemoryDownloadTest() override = default; |
| |
| void SetUp() override { |
| io_thread_.reset(new base::Thread("Network and Blob IO thread")); |
| base::Thread::Options options(base::MessageLoop::TYPE_IO, 0); |
| io_thread_->StartWithOptions(options); |
| |
| base::RunLoop loop; |
| io_thread_->task_runner()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| blob_storage_context_ = |
| std::make_unique<storage::BlobStorageContext>(); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| void TearDown() override { |
| // Say goodbye to |blob_storage_context_| on IO thread. |
| io_thread_->task_runner()->DeleteSoon(FROM_HERE, |
| blob_storage_context_.release()); |
| } |
| |
| protected: |
| // Helper method to create a download with request_params. |
| void CreateDownload(const RequestParams& request_params) { |
| download_ = std::make_unique<InMemoryDownloadImpl>( |
| base::GenerateGUID(), request_params, /* request_body= */ nullptr, |
| TRAFFIC_ANNOTATION_FOR_TESTS, delegate(), &url_loader_factory_, |
| base::BindRepeating(&BlobStorageContextGetter, |
| blob_storage_context_.get()), |
| io_thread_->task_runner()); |
| } |
| |
| InMemoryDownload* download() { return download_.get(); } |
| MockDelegate* delegate() { return &mock_delegate_; } |
| network::TestURLLoaderFactory* url_loader_factory() { |
| return &url_loader_factory_; |
| } |
| |
| // Verifies if data read from |blob| is identical as |expected|. |
| void VerifyBlobData(const std::string& expected, |
| storage::BlobDataHandle* blob) { |
| base::RunLoop run_loop; |
| // BlobReader needs to work on IO thread of BlobStorageContext. |
| io_thread_->task_runner()->PostTaskAndReply( |
| FROM_HERE, |
| base::BindOnce(&InMemoryDownloadTest::VerifyBlobDataOnIO, |
| base::Unretained(this), expected, blob), |
| run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| private: |
| void VerifyBlobDataOnIO(const std::string& expected, |
| storage::BlobDataHandle* blob) { |
| DCHECK(blob); |
| int bytes_read = 0; |
| int async_bytes_read = 0; |
| scoped_refptr<net::IOBuffer> buffer = |
| base::MakeRefCounted<net::IOBuffer>(expected.size()); |
| |
| auto blob_reader = blob->CreateReader(); |
| |
| int blob_size = 0; |
| blob_reader->CalculateSize(base::BindRepeating(&SetValue<int>, &blob_size)); |
| EXPECT_EQ(blob_size, 0) << "In memory blob read data synchronously."; |
| EXPECT_FALSE(blob->IsBeingBuilt()) |
| << "InMemoryDownload ensures blob construction completed."; |
| storage::BlobReader::Status status = blob_reader->Read( |
| buffer.get(), expected.size(), &bytes_read, |
| base::BindRepeating(&SetValue<int>, &async_bytes_read)); |
| EXPECT_EQ(storage::BlobReader::Status::DONE, status); |
| EXPECT_EQ(bytes_read, static_cast<int>(expected.size())); |
| EXPECT_EQ(async_bytes_read, 0); |
| for (size_t i = 0; i < expected.size(); i++) { |
| EXPECT_EQ(expected[i], buffer->data()[i]); |
| } |
| } |
| |
| // IO thread used by network and blob IO tasks. |
| std::unique_ptr<base::Thread> io_thread_; |
| |
| // Created before other objects to provide test environment. |
| base::test::ScopedTaskEnvironment scoped_task_environment_; |
| |
| std::unique_ptr<InMemoryDownloadImpl> download_; |
| NiceMock<MockDelegate> mock_delegate_; |
| |
| // Used by SimpleURLLoader network backend. |
| network::TestURLLoaderFactory url_loader_factory_; |
| |
| // Memory backed blob storage that can never page to disk. |
| std::unique_ptr<storage::BlobStorageContext> blob_storage_context_; |
| |
| DISALLOW_COPY_AND_ASSIGN(InMemoryDownloadTest); |
| }; |
| |
| TEST_F(InMemoryDownloadTest, DownloadTest) { |
| RequestParams request_params; |
| CreateDownload(request_params); |
| url_loader_factory()->AddResponse(request_params.url.spec(), |
| kTestDownloadData); |
| |
| EXPECT_CALL(*delegate(), OnDownloadStarted(_)); |
| // TODO(xingliu): More tests on pause/resume. |
| download()->Start(); |
| delegate()->WaitForCompletion(); |
| |
| EXPECT_EQ(InMemoryDownload::State::COMPLETE, download()->state()); |
| auto blob = download()->ResultAsBlob(); |
| VerifyBlobData(kTestDownloadData, blob.get()); |
| } |
| |
| TEST_F(InMemoryDownloadTest, RedirectResponseHeaders) { |
| RequestParams request_params; |
| request_params.url = GURL("https://example.com/firsturl"); |
| CreateDownload(request_params); |
| |
| // Add a redirect. |
| net::RedirectInfo redirect_info; |
| redirect_info.new_url = GURL("https://example.com/redirect12345"); |
| network::TestURLLoaderFactory::Redirects redirects = { |
| {redirect_info, network::ResourceResponseHead()}}; |
| |
| // Add some random header. |
| network::ResourceResponseHead response_head; |
| response_head.headers = base::MakeRefCounted<net::HttpResponseHeaders>(""); |
| response_head.headers->AddHeader("X-Random-Test-Header: 123"); |
| |
| // The size must match for download as stream from SimpleUrlLoader. |
| network::URLLoaderCompletionStatus status; |
| status.decoded_body_length = base::size(kTestDownloadData) - 1; |
| |
| url_loader_factory()->AddResponse(request_params.url, response_head, |
| kTestDownloadData, status, redirects); |
| |
| std::vector<GURL> expected_url_chain = {request_params.url, |
| redirect_info.new_url}; |
| |
| EXPECT_CALL(*delegate(), |
| OnDownloadStarted(InMemoryDownloadMatcher( |
| response_head.headers->raw_headers(), expected_url_chain))); |
| |
| download()->Start(); |
| delegate()->WaitForCompletion(); |
| EXPECT_EQ(InMemoryDownload::State::COMPLETE, download()->state()); |
| |
| // Verify the response headers and URL chain. The URL chain should contain |
| // the original URL and redirect URL, and should not contain the final URL. |
| EXPECT_EQ(download()->url_chain(), expected_url_chain); |
| EXPECT_EQ(download()->response_headers()->raw_headers(), |
| response_head.headers->raw_headers()); |
| |
| // Verfiy the data persisted to disk after redirect chain. |
| auto blob = download()->ResultAsBlob(); |
| VerifyBlobData(kTestDownloadData, blob.get()); |
| } |
| |
| } // namespace |
| |
| } // namespace download |