// Copyright 2014 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 "base/memory/scoped_ptr.h" #include "base/run_loop.h" #include "base/strings/stringprintf.h" #include "base/threading/sequenced_worker_pool.h" #include "base/threading/worker_pool.h" #include "net/base/request_priority.h" #include "net/url_request/url_request_job.h" #include "net/url_request/url_request_job_factory.h" #include "net/url_request/url_request_job_factory_impl.h" #include "net/url_request/url_request_simple_job.h" #include "net/url_request/url_request_test_util.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { namespace { const char kTestData[] = "Huge data array"; const int kRangeFirstPosition = 5; const int kRangeLastPosition = 8; static_assert(kRangeFirstPosition > 0 && kRangeFirstPosition < kRangeLastPosition && kRangeLastPosition < static_cast(arraysize(kTestData) - 1), "invalid range"); // This function does nothing. void DoNothing() { } class MockSimpleJob : public URLRequestSimpleJob { public: MockSimpleJob(URLRequest* request, NetworkDelegate* network_delegate, scoped_refptr task_runner, std::string data) : URLRequestSimpleJob(request, network_delegate), data_(data), task_runner_(task_runner) {} protected: // URLRequestSimpleJob implementation: int GetData(std::string* mime_type, std::string* charset, std::string* data, const CompletionCallback& callback) const override { mime_type->assign("text/plain"); charset->assign("US-ASCII"); data->assign(data_); return OK; } base::TaskRunner* GetTaskRunner() const override { return task_runner_.get(); } private: ~MockSimpleJob() override {} const std::string data_; scoped_refptr task_runner_; DISALLOW_COPY_AND_ASSIGN(MockSimpleJob); }; class CancelURLRequestDelegate : public URLRequest::Delegate { public: explicit CancelURLRequestDelegate() : buf_(new IOBuffer(kBufferSize)), run_loop_(new base::RunLoop) {} void OnResponseStarted(URLRequest* request) override { int bytes_read = 0; EXPECT_FALSE(request->Read(buf_.get(), kBufferSize, &bytes_read)); EXPECT_TRUE(request->status().is_io_pending()); request->Cancel(); run_loop_->Quit(); } void OnReadCompleted(URLRequest* request, int bytes_read) override {} void WaitUntilHeadersReceived() const { run_loop_->Run(); } private: static const int kBufferSize = 4096; scoped_refptr buf_; scoped_ptr run_loop_; }; class SimpleJobProtocolHandler : public URLRequestJobFactory::ProtocolHandler { public: SimpleJobProtocolHandler(scoped_refptr task_runner) : task_runner_(task_runner) {} URLRequestJob* MaybeCreateJob( URLRequest* request, NetworkDelegate* network_delegate) const override { if (request->url().spec() == "data:empty") return new MockSimpleJob(request, network_delegate, task_runner_, ""); return new MockSimpleJob(request, network_delegate, task_runner_, kTestData); } private: scoped_refptr task_runner_; ~SimpleJobProtocolHandler() override {} }; class URLRequestSimpleJobTest : public ::testing::Test { public: URLRequestSimpleJobTest() : worker_pool_( new base::SequencedWorkerPool(1, "URLRequestSimpleJobTest")), task_runner_(worker_pool_->GetSequencedTaskRunnerWithShutdownBehavior( worker_pool_->GetSequenceToken(), base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)), context_(true) { job_factory_.SetProtocolHandler("data", new SimpleJobProtocolHandler(task_runner_)); context_.set_job_factory(&job_factory_); context_.Init(); request_ = context_.CreateRequest(GURL("data:test"), DEFAULT_PRIORITY, &delegate_); } ~URLRequestSimpleJobTest() override { worker_pool_->Shutdown(); } void StartRequest(const HttpRequestHeaders* headers) { if (headers) request_->SetExtraRequestHeaders(*headers); request_->Start(); EXPECT_TRUE(request_->is_pending()); base::RunLoop().Run(); EXPECT_FALSE(request_->is_pending()); } void TearDown() override { worker_pool_->Shutdown(); } protected: scoped_refptr worker_pool_; scoped_refptr task_runner_; TestURLRequestContext context_; URLRequestJobFactoryImpl job_factory_; TestDelegate delegate_; scoped_ptr request_; }; } // namespace TEST_F(URLRequestSimpleJobTest, SimpleRequest) { StartRequest(NULL); ASSERT_TRUE(request_->status().is_success()); EXPECT_EQ(kTestData, delegate_.data_received()); } TEST_F(URLRequestSimpleJobTest, RangeRequest) { const std::string kExpectedBody = std::string( kTestData + kRangeFirstPosition, kTestData + kRangeLastPosition + 1); HttpRequestHeaders headers; headers.SetHeader( HttpRequestHeaders::kRange, HttpByteRange::Bounded(kRangeFirstPosition, kRangeLastPosition) .GetHeaderValue()); StartRequest(&headers); ASSERT_TRUE(request_->status().is_success()); EXPECT_EQ(kExpectedBody, delegate_.data_received()); } TEST_F(URLRequestSimpleJobTest, MultipleRangeRequest) { HttpRequestHeaders headers; int middle_pos = (kRangeFirstPosition + kRangeLastPosition)/2; std::string range = base::StringPrintf("bytes=%d-%d,%d-%d", kRangeFirstPosition, middle_pos, middle_pos + 1, kRangeLastPosition); headers.SetHeader(HttpRequestHeaders::kRange, range); StartRequest(&headers); EXPECT_TRUE(delegate_.request_failed()); EXPECT_EQ(ERR_REQUEST_RANGE_NOT_SATISFIABLE, request_->status().error()); } TEST_F(URLRequestSimpleJobTest, InvalidRangeRequest) { HttpRequestHeaders headers; std::string range = base::StringPrintf( "bytes=%d-%d", kRangeLastPosition, kRangeFirstPosition); headers.SetHeader(HttpRequestHeaders::kRange, range); StartRequest(&headers); ASSERT_TRUE(request_->status().is_success()); EXPECT_EQ(kTestData, delegate_.data_received()); } TEST_F(URLRequestSimpleJobTest, EmptyDataRequest) { request_ = context_.CreateRequest(GURL("data:empty"), DEFAULT_PRIORITY, &delegate_); StartRequest(nullptr); ASSERT_TRUE(request_->status().is_success()); EXPECT_EQ("", delegate_.data_received()); } TEST_F(URLRequestSimpleJobTest, CancelAfterFirstRead) { scoped_ptr cancel_delegate( new CancelURLRequestDelegate()); request_ = context_.CreateRequest(GURL("data:cancel"), DEFAULT_PRIORITY, cancel_delegate.get()); request_->Start(); cancel_delegate->WaitUntilHeadersReceived(); // Feed a dummy task to the SequencedTaskRunner to make sure that the // callbacks which are invoked in ReadRawData have completed safely. base::RunLoop run_loop; EXPECT_TRUE(task_runner_->PostTaskAndReply(FROM_HERE, base::Bind(&DoNothing), run_loop.QuitClosure())); run_loop.Run(); } } // namespace net