// Copyright (c) 2010 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 "net/http/http_response_body_drainer.h" #include <cstring> #include "base/compiler_specific.h" #include "base/message_loop.h" #include "base/task.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/base/ssl_config_service_defaults.h" #include "net/base/test_completion_callback.h" #include "net/http/http_network_session.h" #include "net/http/http_stream.h" #include "net/proxy/proxy_service.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { namespace { const int kMagicChunkSize = 1024; COMPILE_ASSERT( (HttpResponseBodyDrainer::kDrainBodyBufferSize % kMagicChunkSize) == 0, chunk_size_needs_to_divide_evenly_into_buffer_size); class CloseResultWaiter { public: CloseResultWaiter() : result_(false), have_result_(false), waiting_for_result_(false) {} int WaitForResult() { DCHECK(!waiting_for_result_); while (!have_result_) { waiting_for_result_ = true; MessageLoop::current()->Run(); waiting_for_result_ = false; } return result_; } void set_result(bool result) { result_ = result; have_result_ = true; if (waiting_for_result_) MessageLoop::current()->Quit(); } private: int result_; bool have_result_; bool waiting_for_result_; DISALLOW_COPY_AND_ASSIGN(CloseResultWaiter); }; class MockHttpStream : public HttpStream { public: MockHttpStream(CloseResultWaiter* result_waiter) : result_waiter_(result_waiter), user_callback_(NULL), closed_(false), stall_reads_forever_(false), num_chunks_(0), is_complete_(false), ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {} virtual ~MockHttpStream() {} // HttpStream implementation: virtual int InitializeStream(const HttpRequestInfo* request_info, const BoundNetLog& net_log, CompletionCallback* callback) OVERRIDE { return ERR_UNEXPECTED; } virtual int SendRequest(const HttpRequestHeaders& request_headers, UploadDataStream* request_body, HttpResponseInfo* response, CompletionCallback* callback) OVERRIDE { return ERR_UNEXPECTED; } virtual uint64 GetUploadProgress() const OVERRIDE { return 0; } virtual int ReadResponseHeaders(CompletionCallback* callback) OVERRIDE { return ERR_UNEXPECTED; } virtual const HttpResponseInfo* GetResponseInfo() const OVERRIDE { return NULL; } virtual bool CanFindEndOfResponse() const OVERRIDE { return true; } virtual bool IsMoreDataBuffered() const OVERRIDE { return false; } virtual bool IsConnectionReused() const OVERRIDE { return false; } virtual void SetConnectionReused() OVERRIDE {} virtual bool IsConnectionReusable() const OVERRIDE { return false; } virtual void GetSSLInfo(SSLInfo* ssl_info) OVERRIDE {} virtual void GetSSLCertRequestInfo( SSLCertRequestInfo* cert_request_info) OVERRIDE {} // Mocked API virtual int ReadResponseBody(IOBuffer* buf, int buf_len, CompletionCallback* callback) OVERRIDE; virtual void Close(bool not_reusable) OVERRIDE { DCHECK(!closed_); closed_ = true; result_waiter_->set_result(not_reusable); } virtual HttpStream* RenewStreamForAuth() OVERRIDE { return NULL; } virtual bool IsResponseBodyComplete() const OVERRIDE { return is_complete_; } virtual bool IsSpdyHttpStream() const OVERRIDE { return false; } // Methods to tweak/observer mock behavior: void StallReadsForever() { stall_reads_forever_ = true; } void set_num_chunks(int num_chunks) { num_chunks_ = num_chunks; } private: void CompleteRead(); bool closed() const { return closed_; } CloseResultWaiter* const result_waiter_; scoped_refptr<IOBuffer> user_buf_; CompletionCallback* user_callback_; bool closed_; bool stall_reads_forever_; int num_chunks_; bool is_complete_; ScopedRunnableMethodFactory<MockHttpStream> method_factory_; }; int MockHttpStream::ReadResponseBody( IOBuffer* buf, int buf_len, CompletionCallback* callback) { DCHECK(callback); DCHECK(!user_callback_); DCHECK(buf); if (stall_reads_forever_) return ERR_IO_PENDING; if (num_chunks_ == 0) return ERR_UNEXPECTED; if (buf_len > kMagicChunkSize && num_chunks_ > 1) { user_buf_ = buf; user_callback_ = callback; MessageLoop::current()->PostTask( FROM_HERE, method_factory_.NewRunnableMethod(&MockHttpStream::CompleteRead)); return ERR_IO_PENDING; } num_chunks_--; if (!num_chunks_) is_complete_ = true; return buf_len; } void MockHttpStream::CompleteRead() { CompletionCallback* callback = user_callback_; std::memset(user_buf_->data(), 1, kMagicChunkSize); user_buf_ = NULL; user_callback_ = NULL; num_chunks_--; if (!num_chunks_) is_complete_ = true; callback->Run(kMagicChunkSize); } class HttpResponseBodyDrainerTest : public testing::Test { protected: HttpResponseBodyDrainerTest() : proxy_service_(ProxyService::CreateDirect()), ssl_config_service_(new SSLConfigServiceDefaults), session_(CreateNetworkSession()), mock_stream_(new MockHttpStream(&result_waiter_)), drainer_(new HttpResponseBodyDrainer(mock_stream_)) {} ~HttpResponseBodyDrainerTest() {} HttpNetworkSession* CreateNetworkSession() const { HttpNetworkSession::Params params; params.proxy_service = proxy_service_; params.ssl_config_service = ssl_config_service_; return new HttpNetworkSession(params); } scoped_refptr<ProxyService> proxy_service_; scoped_refptr<SSLConfigService> ssl_config_service_; const scoped_refptr<HttpNetworkSession> session_; CloseResultWaiter result_waiter_; MockHttpStream* const mock_stream_; // Owned by |drainer_|. HttpResponseBodyDrainer* const drainer_; // Deletes itself. }; TEST_F(HttpResponseBodyDrainerTest, DrainBodySyncOK) { mock_stream_->set_num_chunks(1); drainer_->Start(session_); EXPECT_FALSE(result_waiter_.WaitForResult()); } TEST_F(HttpResponseBodyDrainerTest, DrainBodyAsyncOK) { mock_stream_->set_num_chunks(3); drainer_->Start(session_); EXPECT_FALSE(result_waiter_.WaitForResult()); } TEST_F(HttpResponseBodyDrainerTest, DrainBodySizeEqualsDrainBuffer) { mock_stream_->set_num_chunks( HttpResponseBodyDrainer::kDrainBodyBufferSize / kMagicChunkSize); drainer_->Start(session_); EXPECT_FALSE(result_waiter_.WaitForResult()); } TEST_F(HttpResponseBodyDrainerTest, DrainBodyTimeOut) { mock_stream_->set_num_chunks(2); mock_stream_->StallReadsForever(); drainer_->Start(session_); EXPECT_TRUE(result_waiter_.WaitForResult()); } TEST_F(HttpResponseBodyDrainerTest, CancelledBySession) { mock_stream_->set_num_chunks(2); mock_stream_->StallReadsForever(); drainer_->Start(session_); // HttpNetworkSession should delete |drainer_|. } TEST_F(HttpResponseBodyDrainerTest, DrainBodyTooLarge) { TestCompletionCallback callback; int too_many_chunks = HttpResponseBodyDrainer::kDrainBodyBufferSize / kMagicChunkSize; too_many_chunks += 1; // Now it's too large. mock_stream_->set_num_chunks(too_many_chunks); drainer_->Start(session_); EXPECT_TRUE(result_waiter_.WaitForResult()); } } // namespace } // namespace net