blob: 651726b6b90824fed9741c5659235f285d892b3c [file] [log] [blame]
// Copyright (c) 2012 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 "android_webview/browser/input_stream.h"
#include "android_webview/browser/net/android_stream_reader_url_request_job.h"
#include "android_webview/browser/net/aw_url_request_job_factory.h"
#include "android_webview/browser/net/input_stream_reader.h"
#include "base/format_macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "net/base/request_priority.h"
#include "net/http/http_byte_range.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using android_webview::InputStream;
using android_webview::InputStreamReader;
using net::TestDelegate;
using net::TestJobInterceptor;
using net::TestNetworkDelegate;
using net::TestURLRequestContext;
using net::TestURLRequest;
using testing::DoAll;
using testing::Ge;
using testing::Gt;
using testing::InSequence;
using testing::Invoke;
using testing::InvokeWithoutArgs;
using testing::NotNull;
using testing::Return;
using testing::SaveArg;
using testing::SetArgPointee;
using testing::StrictMock;
using testing::Test;
using testing::WithArg;
using testing::WithArgs;
using testing::_;
// Some of the classes will DCHECK on a null InputStream (which is desirable).
// The workaround is to use this class. None of the methods need to be
// implemented as the mock InputStreamReader should never forward calls to the
// InputStream.
class NotImplInputStream : public InputStream {
public:
NotImplInputStream() {}
virtual ~NotImplInputStream() {}
virtual bool BytesAvailable(int* bytes_available) const OVERRIDE {
NOTIMPLEMENTED();
return false;
}
virtual bool Skip(int64_t n, int64_t* bytes_skipped) OVERRIDE {
NOTIMPLEMENTED();
return false;
}
virtual bool Read(net::IOBuffer* dest, int length, int* bytes_read) OVERRIDE {
NOTIMPLEMENTED();
return false;
}
};
// Required in order to create an instance of AndroidStreamReaderURLRequestJob.
class StreamReaderDelegate :
public AndroidStreamReaderURLRequestJob::Delegate {
public:
StreamReaderDelegate() {}
virtual scoped_ptr<InputStream> OpenInputStream(
JNIEnv* env,
const GURL& url) OVERRIDE {
return make_scoped_ptr<InputStream>(new NotImplInputStream());
}
virtual void OnInputStreamOpenFailed(net::URLRequest* request,
bool* restart) OVERRIDE {
*restart = false;
}
virtual bool GetMimeType(JNIEnv* env,
net::URLRequest* request,
android_webview::InputStream* stream,
std::string* mime_type) OVERRIDE {
return false;
}
virtual bool GetCharset(JNIEnv* env,
net::URLRequest* request,
android_webview::InputStream* stream,
std::string* charset) OVERRIDE {
return false;
}
};
class NullStreamReaderDelegate : public StreamReaderDelegate {
public:
NullStreamReaderDelegate() {}
virtual scoped_ptr<InputStream> OpenInputStream(
JNIEnv* env,
const GURL& url) OVERRIDE {
return make_scoped_ptr<InputStream>(NULL);
}
};
class MockInputStreamReader : public InputStreamReader {
public:
MockInputStreamReader() : InputStreamReader(new NotImplInputStream()) {}
~MockInputStreamReader() {}
MOCK_METHOD1(Seek, int(const net::HttpByteRange& byte_range));
MOCK_METHOD2(ReadRawData, int(net::IOBuffer* buffer, int buffer_size));
};
class TestStreamReaderJob : public AndroidStreamReaderURLRequestJob {
public:
TestStreamReaderJob(
net::URLRequest* request,
net::NetworkDelegate* network_delegate,
scoped_ptr<Delegate> delegate,
scoped_ptr<InputStreamReader> stream_reader)
: AndroidStreamReaderURLRequestJob(request,
network_delegate,
delegate.Pass()),
stream_reader_(stream_reader.Pass()) {
message_loop_proxy_ = base::MessageLoopProxy::current();
}
virtual scoped_ptr<InputStreamReader> CreateStreamReader(
InputStream* stream) OVERRIDE {
return stream_reader_.Pass();
}
protected:
virtual ~TestStreamReaderJob() {}
virtual base::TaskRunner* GetWorkerThreadRunner() OVERRIDE {
return message_loop_proxy_.get();
}
scoped_ptr<InputStreamReader> stream_reader_;
scoped_refptr<base::MessageLoopProxy> message_loop_proxy_;
};
class AndroidStreamReaderURLRequestJobTest : public Test {
public:
AndroidStreamReaderURLRequestJobTest() : loop_(base::MessageLoop::TYPE_IO) {}
protected:
virtual void SetUp() {
context_.set_job_factory(&factory_);
context_.set_network_delegate(&network_delegate_);
req_.reset(new TestURLRequest(GURL("content://foo"),
net::DEFAULT_PRIORITY,
&url_request_delegate_,
&context_));
req_->set_method("GET");
}
void SetRange(net::URLRequest* req, int first_byte, int last_byte) {
net::HttpRequestHeaders headers;
headers.SetHeader(net::HttpRequestHeaders::kRange,
net::HttpByteRange::Bounded(
first_byte, last_byte).GetHeaderValue());
req->SetExtraRequestHeaders(headers);
}
void SetUpTestJob(scoped_ptr<InputStreamReader> stream_reader) {
SetUpTestJob(stream_reader.Pass(),
make_scoped_ptr(new StreamReaderDelegate())
.PassAs<AndroidStreamReaderURLRequestJob::Delegate>());
}
void SetUpTestJob(scoped_ptr<InputStreamReader> stream_reader,
scoped_ptr<AndroidStreamReaderURLRequestJob::Delegate>
stream_reader_delegate) {
TestStreamReaderJob* test_stream_reader_job =
new TestStreamReaderJob(
req_.get(),
&network_delegate_,
stream_reader_delegate.Pass(),
stream_reader.Pass());
// The Interceptor is owned by the |factory_|.
TestJobInterceptor* protocol_handler = new TestJobInterceptor;
protocol_handler->set_main_intercept_job(test_stream_reader_job);
bool set_protocol = factory_.SetProtocolHandler("http", protocol_handler);
DCHECK(set_protocol);
protocol_handler = new TestJobInterceptor;
protocol_handler->set_main_intercept_job(test_stream_reader_job);
set_protocol = factory_.SetProtocolHandler("content", protocol_handler);
DCHECK(set_protocol);
}
base::MessageLoop loop_;
TestURLRequestContext context_;
android_webview::AwURLRequestJobFactory factory_;
TestDelegate url_request_delegate_;
TestNetworkDelegate network_delegate_;
scoped_ptr<TestURLRequest> req_;
};
TEST_F(AndroidStreamReaderURLRequestJobTest, ReadEmptyStream) {
scoped_ptr<StrictMock<MockInputStreamReader> > stream_reader(
new StrictMock<MockInputStreamReader>());
{
InSequence s;
EXPECT_CALL(*stream_reader, Seek(_))
.WillOnce(Return(0));
EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Gt(0)))
.WillOnce(Return(0));
}
SetUpTestJob(stream_reader.PassAs<InputStreamReader>());
req_->Start();
// The TestDelegate will quit the message loop on request completion.
base::MessageLoop::current()->Run();
EXPECT_FALSE(url_request_delegate_.request_failed());
EXPECT_EQ(1, network_delegate_.completed_requests());
EXPECT_EQ(0, network_delegate_.error_count());
EXPECT_EQ(200, req_->GetResponseCode());
}
TEST_F(AndroidStreamReaderURLRequestJobTest, ReadWithNullStream) {
SetUpTestJob(scoped_ptr<InputStreamReader>(),
make_scoped_ptr(new NullStreamReaderDelegate())
.PassAs<AndroidStreamReaderURLRequestJob::Delegate>());
req_->Start();
// The TestDelegate will quit the message loop on request completion.
base::MessageLoop::current()->Run();
// The request_failed() method is named confusingly but all it checks is
// whether the request got as far as calling NotifyHeadersComplete.
EXPECT_FALSE(url_request_delegate_.request_failed());
EXPECT_EQ(1, network_delegate_.completed_requests());
// A null input stream shouldn't result in an error. See crbug.com/180950.
EXPECT_EQ(0, network_delegate_.error_count());
EXPECT_EQ(404, req_->GetResponseCode());
}
TEST_F(AndroidStreamReaderURLRequestJobTest, ReadPartOfStream) {
const int bytes_available = 128;
const int offset = 32;
const int bytes_to_read = bytes_available - offset;
scoped_ptr<StrictMock<MockInputStreamReader> > stream_reader(
new StrictMock<MockInputStreamReader>());
{
InSequence s;
EXPECT_CALL(*stream_reader, Seek(_))
.WillOnce(Return(bytes_available));
EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
.WillOnce(Return(bytes_to_read/2));
EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
.WillOnce(Return(bytes_to_read/2));
EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
.WillOnce(Return(0));
}
SetUpTestJob(stream_reader.PassAs<InputStreamReader>());
SetRange(req_.get(), offset, bytes_available);
req_->Start();
base::MessageLoop::current()->Run();
EXPECT_FALSE(url_request_delegate_.request_failed());
EXPECT_EQ(bytes_to_read, url_request_delegate_.bytes_received());
EXPECT_EQ(1, network_delegate_.completed_requests());
EXPECT_EQ(0, network_delegate_.error_count());
}
TEST_F(AndroidStreamReaderURLRequestJobTest,
ReadStreamWithMoreAvailableThanActual) {
const int bytes_available_reported = 190;
const int bytes_available = 128;
const int offset = 0;
const int bytes_to_read = bytes_available - offset;
scoped_ptr<StrictMock<MockInputStreamReader> > stream_reader(
new StrictMock<MockInputStreamReader>());
{
InSequence s;
EXPECT_CALL(*stream_reader, Seek(_))
.WillOnce(Return(bytes_available_reported));
EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
.WillOnce(Return(bytes_available));
EXPECT_CALL(*stream_reader, ReadRawData(NotNull(), Ge(bytes_to_read)))
.WillOnce(Return(0));
}
SetUpTestJob(stream_reader.PassAs<InputStreamReader>());
SetRange(req_.get(), offset, bytes_available_reported);
req_->Start();
base::MessageLoop::current()->Run();
EXPECT_FALSE(url_request_delegate_.request_failed());
EXPECT_EQ(bytes_to_read, url_request_delegate_.bytes_received());
EXPECT_EQ(1, network_delegate_.completed_requests());
EXPECT_EQ(0, network_delegate_.error_count());
}
TEST_F(AndroidStreamReaderURLRequestJobTest, DeleteJobMidWaySeek) {
const int offset = 20;
const int bytes_available = 128;
base::RunLoop loop;
scoped_ptr<StrictMock<MockInputStreamReader> > stream_reader(
new StrictMock<MockInputStreamReader>());
EXPECT_CALL(*stream_reader, Seek(_))
.WillOnce(DoAll(InvokeWithoutArgs(&loop, &base::RunLoop::Quit),
Return(bytes_available)));
ON_CALL(*stream_reader, ReadRawData(_, _))
.WillByDefault(Return(0));
SetUpTestJob(stream_reader.PassAs<InputStreamReader>());
SetRange(req_.get(), offset, bytes_available);
req_->Start();
loop.Run();
EXPECT_EQ(0, network_delegate_.completed_requests());
req_->Cancel();
EXPECT_EQ(1, network_delegate_.completed_requests());
}
TEST_F(AndroidStreamReaderURLRequestJobTest, DeleteJobMidWayRead) {
const int offset = 20;
const int bytes_available = 128;
base::RunLoop loop;
scoped_ptr<StrictMock<MockInputStreamReader> > stream_reader(
new StrictMock<MockInputStreamReader>());
net::CompletionCallback read_completion_callback;
EXPECT_CALL(*stream_reader, Seek(_))
.WillOnce(Return(bytes_available));
EXPECT_CALL(*stream_reader, ReadRawData(_, _))
.WillOnce(DoAll(InvokeWithoutArgs(&loop, &base::RunLoop::Quit),
Return(bytes_available)));
SetUpTestJob(stream_reader.PassAs<InputStreamReader>());
SetRange(req_.get(), offset, bytes_available);
req_->Start();
loop.Run();
EXPECT_EQ(0, network_delegate_.completed_requests());
req_->Cancel();
EXPECT_EQ(1, network_delegate_.completed_requests());
}