|  | // 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 <stdint.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #include <stack> | 
|  | #include <string> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/bind_helpers.h" | 
|  | #include "base/callback.h" | 
|  | #include "base/compiler_specific.h" | 
|  | #include "base/location.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/pickle.h" | 
|  | #include "base/single_thread_task_runner.h" | 
|  | #include "base/synchronization/waitable_event.h" | 
|  | #include "base/threading/thread.h" | 
|  | #include "base/threading/thread_task_runner_handle.h" | 
|  | #include "content/browser/appcache/appcache_response.h" | 
|  | #include "content/browser/appcache/mock_appcache_service.h" | 
|  | #include "net/base/io_buffer.h" | 
|  | #include "net/base/net_errors.h" | 
|  | #include "net/http/http_response_headers.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | using net::IOBuffer; | 
|  | using net::WrappedIOBuffer; | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | static const int kNumBlocks = 4; | 
|  | static const int kBlockSize = 1024; | 
|  | static const int kNoSuchResponseId = 123; | 
|  |  | 
|  | class AppCacheResponseTest : public testing::Test { | 
|  | public: | 
|  | // Test Harness ------------------------------------------------------------- | 
|  |  | 
|  | // Helper class used to verify test results | 
|  | class MockStorageDelegate : public AppCacheStorage::Delegate { | 
|  | public: | 
|  | explicit MockStorageDelegate(AppCacheResponseTest* test) | 
|  | : loaded_info_id_(0), test_(test) { | 
|  | } | 
|  |  | 
|  | void OnResponseInfoLoaded(AppCacheResponseInfo* info, | 
|  | int64_t response_id) override { | 
|  | loaded_info_ = info; | 
|  | loaded_info_id_ = response_id; | 
|  | test_->ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | scoped_refptr<AppCacheResponseInfo> loaded_info_; | 
|  | int64_t loaded_info_id_; | 
|  | AppCacheResponseTest* test_; | 
|  | }; | 
|  |  | 
|  | // Helper callback to run a test on our io_thread. The io_thread is spun up | 
|  | // once and reused for all tests. | 
|  | template <class Method> | 
|  | void MethodWrapper(Method method) { | 
|  | SetUpTest(); | 
|  | (this->*method)(); | 
|  | } | 
|  |  | 
|  | static void SetUpTestCase() { | 
|  | io_thread_.reset(new base::Thread("AppCacheResponseTest Thread")); | 
|  | base::Thread::Options options(base::MessageLoop::TYPE_IO, 0); | 
|  | io_thread_->StartWithOptions(options); | 
|  | } | 
|  |  | 
|  | static void TearDownTestCase() { | 
|  | io_thread_.reset(NULL); | 
|  | } | 
|  |  | 
|  | AppCacheResponseTest() {} | 
|  |  | 
|  | template <class Method> | 
|  | void RunTestOnIOThread(Method method) { | 
|  | test_finished_event_.reset(new base::WaitableEvent( | 
|  | base::WaitableEvent::ResetPolicy::AUTOMATIC, | 
|  | base::WaitableEvent::InitialState::NOT_SIGNALED)); | 
|  | io_thread_->task_runner()->PostTask( | 
|  | FROM_HERE, base::Bind(&AppCacheResponseTest::MethodWrapper<Method>, | 
|  | base::Unretained(this), method)); | 
|  | test_finished_event_->Wait(); | 
|  | } | 
|  |  | 
|  | void SetUpTest() { | 
|  | DCHECK(base::MessageLoop::current() == io_thread_->message_loop()); | 
|  | DCHECK(task_stack_.empty()); | 
|  | storage_delegate_.reset(new MockStorageDelegate(this)); | 
|  | service_.reset(new MockAppCacheService()); | 
|  | expected_read_result_ = 0; | 
|  | expected_write_result_ = 0; | 
|  | written_response_id_ = 0; | 
|  | should_delete_reader_in_completion_callback_ = false; | 
|  | should_delete_writer_in_completion_callback_ = false; | 
|  | reader_deletion_count_down_ = 0; | 
|  | writer_deletion_count_down_ = 0; | 
|  | read_callback_was_called_ = false; | 
|  | write_callback_was_called_ = false; | 
|  | } | 
|  |  | 
|  | void TearDownTest() { | 
|  | DCHECK(base::MessageLoop::current() == io_thread_->message_loop()); | 
|  | while (!task_stack_.empty()) | 
|  | task_stack_.pop(); | 
|  |  | 
|  | reader_.reset(); | 
|  | read_buffer_ = NULL; | 
|  | read_info_buffer_ = NULL; | 
|  | writer_.reset(); | 
|  | write_buffer_ = NULL; | 
|  | write_info_buffer_ = NULL; | 
|  | storage_delegate_.reset(); | 
|  | service_.reset(); | 
|  | } | 
|  |  | 
|  | void TestFinished() { | 
|  | // We unwind the stack prior to finishing up to let stack | 
|  | // based objects get deleted. | 
|  | DCHECK(base::MessageLoop::current() == io_thread_->message_loop()); | 
|  | base::ThreadTaskRunnerHandle::Get()->PostTask( | 
|  | FROM_HERE, base::Bind(&AppCacheResponseTest::TestFinishedUnwound, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void TestFinishedUnwound() { | 
|  | TearDownTest(); | 
|  | test_finished_event_->Signal(); | 
|  | } | 
|  |  | 
|  | void PushNextTask(const base::Closure& task) { | 
|  | task_stack_.push(std::pair<base::Closure, bool>(task, false)); | 
|  | } | 
|  |  | 
|  | void PushNextTaskAsImmediate(const base::Closure& task) { | 
|  | task_stack_.push(std::pair<base::Closure, bool>(task, true)); | 
|  | } | 
|  |  | 
|  | void ScheduleNextTask() { | 
|  | DCHECK(base::MessageLoop::current() == io_thread_->message_loop()); | 
|  | if (task_stack_.empty()) { | 
|  | TestFinished(); | 
|  | return; | 
|  | } | 
|  | base::Closure task = task_stack_.top().first; | 
|  | bool immediate = task_stack_.top().second; | 
|  | task_stack_.pop(); | 
|  | if (immediate) | 
|  | task.Run(); | 
|  | else | 
|  | base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, task); | 
|  | } | 
|  |  | 
|  | // Wrappers to call AppCacheResponseReader/Writer Read and Write methods | 
|  |  | 
|  | void WriteBasicResponse() { | 
|  | static const char kHttpHeaders[] = | 
|  | "HTTP/1.0 200 OK\0Content-Length: 5\0\0"; | 
|  | static const char kHttpBody[] = "Hello"; | 
|  | scoped_refptr<IOBuffer> body(new WrappedIOBuffer(kHttpBody)); | 
|  | std::string raw_headers(kHttpHeaders, arraysize(kHttpHeaders)); | 
|  | WriteResponse( | 
|  | MakeHttpResponseInfo(raw_headers), body.get(), strlen(kHttpBody)); | 
|  | } | 
|  |  | 
|  | int basic_response_size() { return 5; }  // should match kHttpBody above | 
|  |  | 
|  | void WriteResponse(net::HttpResponseInfo* head, | 
|  | IOBuffer* body, int body_len) { | 
|  | DCHECK(body); | 
|  | scoped_refptr<IOBuffer> body_ref(body); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::WriteResponseBody, | 
|  | base::Unretained(this), body_ref, body_len)); | 
|  | WriteResponseHead(head); | 
|  | } | 
|  |  | 
|  | void WriteResponseHead(net::HttpResponseInfo* head) { | 
|  | EXPECT_FALSE(writer_->IsWritePending()); | 
|  | expected_write_result_ = GetHttpResponseInfoSize(head); | 
|  | write_info_buffer_ = new HttpResponseInfoIOBuffer(head); | 
|  | writer_->WriteInfo(write_info_buffer_.get(), | 
|  | base::Bind(&AppCacheResponseTest::OnWriteInfoComplete, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void WriteResponseBody(scoped_refptr<IOBuffer> io_buffer, int buf_len) { | 
|  | EXPECT_FALSE(writer_->IsWritePending()); | 
|  | write_buffer_ = io_buffer; | 
|  | expected_write_result_ = buf_len; | 
|  | writer_->WriteData(write_buffer_.get(), | 
|  | buf_len, | 
|  | base::Bind(&AppCacheResponseTest::OnWriteComplete, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void WriteResponseMetadata(scoped_refptr<IOBuffer> io_buffer, int buf_len) { | 
|  | EXPECT_FALSE(metadata_writer_->IsWritePending()); | 
|  | write_buffer_ = io_buffer; | 
|  | expected_write_result_ = buf_len; | 
|  | metadata_writer_->WriteMetadata( | 
|  | write_buffer_.get(), buf_len, | 
|  | base::Bind(&AppCacheResponseTest::OnMetadataWriteComplete, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void ReadResponseBody(scoped_refptr<IOBuffer> io_buffer, int buf_len) { | 
|  | EXPECT_FALSE(reader_->IsReadPending()); | 
|  | read_buffer_ = io_buffer; | 
|  | expected_read_result_ = buf_len; | 
|  | reader_->ReadData(read_buffer_.get(), | 
|  | buf_len, | 
|  | base::Bind(&AppCacheResponseTest::OnReadComplete, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | // AppCacheResponseReader / Writer completion callbacks | 
|  |  | 
|  | void OnWriteInfoComplete(int result) { | 
|  | EXPECT_FALSE(writer_->IsWritePending()); | 
|  | EXPECT_EQ(expected_write_result_, result); | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void OnWriteComplete(int result) { | 
|  | EXPECT_FALSE(writer_->IsWritePending()); | 
|  | write_callback_was_called_ = true; | 
|  | EXPECT_EQ(expected_write_result_, result); | 
|  | if (should_delete_writer_in_completion_callback_ && | 
|  | --writer_deletion_count_down_ == 0) { | 
|  | writer_.reset(); | 
|  | } | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void OnMetadataWriteComplete(int result) { | 
|  | EXPECT_FALSE(metadata_writer_->IsWritePending()); | 
|  | EXPECT_EQ(expected_write_result_, result); | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void OnReadInfoComplete(int result) { | 
|  | EXPECT_FALSE(reader_->IsReadPending()); | 
|  | EXPECT_EQ(expected_read_result_, result); | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void OnReadComplete(int result) { | 
|  | EXPECT_FALSE(reader_->IsReadPending()); | 
|  | read_callback_was_called_ = true; | 
|  | EXPECT_EQ(expected_read_result_, result); | 
|  | if (should_delete_reader_in_completion_callback_ && | 
|  | --reader_deletion_count_down_ == 0) { | 
|  | reader_.reset(); | 
|  | } | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | // Helpers to work with HttpResponseInfo objects | 
|  |  | 
|  | net::HttpResponseInfo* MakeHttpResponseInfo(const std::string& raw_headers) { | 
|  | net::HttpResponseInfo* info = new net::HttpResponseInfo; | 
|  | info->request_time = base::Time::Now(); | 
|  | info->response_time = base::Time::Now(); | 
|  | info->was_cached = false; | 
|  | info->headers = new net::HttpResponseHeaders(raw_headers); | 
|  | return info; | 
|  | } | 
|  |  | 
|  | int GetHttpResponseInfoSize(const net::HttpResponseInfo* info) { | 
|  | base::Pickle pickle; | 
|  | return PickleHttpResonseInfo(&pickle, info); | 
|  | } | 
|  |  | 
|  | bool CompareHttpResponseInfos(const net::HttpResponseInfo* info1, | 
|  | const net::HttpResponseInfo* info2) { | 
|  | base::Pickle pickle1; | 
|  | base::Pickle pickle2; | 
|  | PickleHttpResonseInfo(&pickle1, info1); | 
|  | PickleHttpResonseInfo(&pickle2, info2); | 
|  | return (pickle1.size() == pickle2.size()) && | 
|  | (0 == memcmp(pickle1.data(), pickle2.data(), pickle1.size())); | 
|  | } | 
|  |  | 
|  | int PickleHttpResonseInfo(base::Pickle* pickle, | 
|  | const net::HttpResponseInfo* info) { | 
|  | const bool kSkipTransientHeaders = true; | 
|  | const bool kTruncated = false; | 
|  | info->Persist(pickle, kSkipTransientHeaders, kTruncated); | 
|  | return pickle->size(); | 
|  | } | 
|  |  | 
|  | // Helpers to fill and verify blocks of memory with a value | 
|  |  | 
|  | void FillData(char value, char* data, int data_len) { | 
|  | memset(data, value, data_len); | 
|  | } | 
|  |  | 
|  | bool CheckData(char value, const char* data, int data_len) { | 
|  | for (int i = 0; i < data_len; ++i, ++data) { | 
|  | if (*data != value) | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Individual Tests --------------------------------------------------------- | 
|  | // Most of the individual tests involve multiple async steps. Each test | 
|  | // is delineated with a section header. | 
|  |  | 
|  |  | 
|  | // ReadNonExistentResponse ------------------------------------------- | 
|  | void ReadNonExistentResponse() { | 
|  | // 1. Attempt to ReadInfo | 
|  | // 2. Attempt to ReadData | 
|  |  | 
|  | reader_.reset( | 
|  | service_->storage()->CreateResponseReader(GURL(), kNoSuchResponseId)); | 
|  |  | 
|  | // Push tasks in reverse order | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::ReadNonExistentData, | 
|  | base::Unretained(this))); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::ReadNonExistentInfo, | 
|  | base::Unretained(this))); | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void ReadNonExistentInfo() { | 
|  | EXPECT_FALSE(reader_->IsReadPending()); | 
|  | read_info_buffer_ = new HttpResponseInfoIOBuffer(); | 
|  | reader_->ReadInfo(read_info_buffer_.get(), | 
|  | base::Bind(&AppCacheResponseTest::OnReadInfoComplete, | 
|  | base::Unretained(this))); | 
|  | EXPECT_TRUE(reader_->IsReadPending()); | 
|  | expected_read_result_ = net::ERR_CACHE_MISS; | 
|  | } | 
|  |  | 
|  | void ReadNonExistentData() { | 
|  | EXPECT_FALSE(reader_->IsReadPending()); | 
|  | read_buffer_ = new IOBuffer(kBlockSize); | 
|  | reader_->ReadData(read_buffer_.get(), | 
|  | kBlockSize, | 
|  | base::Bind(&AppCacheResponseTest::OnReadComplete, | 
|  | base::Unretained(this))); | 
|  | EXPECT_TRUE(reader_->IsReadPending()); | 
|  | expected_read_result_ = net::ERR_CACHE_MISS; | 
|  | } | 
|  |  | 
|  | // LoadResponseInfo_Miss ---------------------------------------------------- | 
|  | void LoadResponseInfo_Miss() { | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::LoadResponseInfo_Miss_Verify, | 
|  | base::Unretained(this))); | 
|  | service_->storage()->LoadResponseInfo(GURL(), kNoSuchResponseId, | 
|  | storage_delegate_.get()); | 
|  | } | 
|  |  | 
|  | void LoadResponseInfo_Miss_Verify() { | 
|  | EXPECT_EQ(kNoSuchResponseId, storage_delegate_->loaded_info_id_); | 
|  | EXPECT_TRUE(!storage_delegate_->loaded_info_.get()); | 
|  | TestFinished(); | 
|  | } | 
|  |  | 
|  | // LoadResponseInfo_Hit ---------------------------------------------------- | 
|  | void LoadResponseInfo_Hit() { | 
|  | // This tests involves multiple async steps. | 
|  | // 1. Write a response headers and body to storage | 
|  | //   a. headers | 
|  | //   b. body | 
|  | // 2. Use LoadResponseInfo to read the response headers back out | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::LoadResponseInfo_Hit_Step2, | 
|  | base::Unretained(this))); | 
|  | writer_.reset(service_->storage()->CreateResponseWriter(GURL())); | 
|  | written_response_id_ = writer_->response_id(); | 
|  | WriteBasicResponse(); | 
|  | } | 
|  |  | 
|  | void LoadResponseInfo_Hit_Step2() { | 
|  | writer_.reset(); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::LoadResponseInfo_Hit_Verify, | 
|  | base::Unretained(this))); | 
|  | service_->storage()->LoadResponseInfo(GURL(), written_response_id_, | 
|  | storage_delegate_.get()); | 
|  | } | 
|  |  | 
|  | void LoadResponseInfo_Hit_Verify() { | 
|  | EXPECT_EQ(written_response_id_, storage_delegate_->loaded_info_id_); | 
|  | EXPECT_TRUE(storage_delegate_->loaded_info_.get()); | 
|  | EXPECT_TRUE(CompareHttpResponseInfos( | 
|  | write_info_buffer_->http_info.get(), | 
|  | storage_delegate_->loaded_info_->http_response_info())); | 
|  | EXPECT_EQ(basic_response_size(), | 
|  | storage_delegate_->loaded_info_->response_data_size()); | 
|  | TestFinished(); | 
|  | } | 
|  |  | 
|  | // Metadata ------------------------------------------------- | 
|  | void Metadata() { | 
|  | // This tests involves multiple async steps. | 
|  | // 1. Write a response headers and body to storage | 
|  | //   a. headers | 
|  | //   b. body | 
|  | // 2. Write metadata "Metadata First" using AppCacheResponseMetadataWriter. | 
|  | // 3. Check metadata was written. | 
|  | // 4. Write metadata "Second". | 
|  | // 5. Check metadata was written and was truncated . | 
|  | // 6. Write metadata "". | 
|  | // 7. Check metadata was deleted. | 
|  |  | 
|  | // Push tasks in reverse order. | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_VerifyMetadata, | 
|  | base::Unretained(this), "")); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_LoadResponseInfo, | 
|  | base::Unretained(this))); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_WriteMetadata, | 
|  | base::Unretained(this), "")); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_VerifyMetadata, | 
|  | base::Unretained(this), "Second")); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_LoadResponseInfo, | 
|  | base::Unretained(this))); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_WriteMetadata, | 
|  | base::Unretained(this), "Second")); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_VerifyMetadata, | 
|  | base::Unretained(this), "Metadata First")); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_LoadResponseInfo, | 
|  | base::Unretained(this))); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_WriteMetadata, | 
|  | base::Unretained(this), "Metadata First")); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::Metadata_ResetWriter, | 
|  | base::Unretained(this))); | 
|  | writer_.reset(service_->storage()->CreateResponseWriter(GURL())); | 
|  | written_response_id_ = writer_->response_id(); | 
|  | WriteBasicResponse(); | 
|  | } | 
|  |  | 
|  | void Metadata_ResetWriter() { | 
|  | writer_.reset(); | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void Metadata_WriteMetadata(const char* metadata) { | 
|  | metadata_writer_.reset(service_->storage()->CreateResponseMetadataWriter( | 
|  | written_response_id_)); | 
|  | scoped_refptr<IOBuffer> buffer(new WrappedIOBuffer(metadata)); | 
|  | WriteResponseMetadata(buffer.get(), strlen(metadata)); | 
|  | } | 
|  |  | 
|  | void Metadata_LoadResponseInfo() { | 
|  | metadata_writer_.reset(); | 
|  | storage_delegate_.reset(new MockStorageDelegate(this)); | 
|  | service_->storage()->LoadResponseInfo(GURL(), written_response_id_, | 
|  | storage_delegate_.get()); | 
|  | } | 
|  |  | 
|  | void Metadata_VerifyMetadata(const char* metadata) { | 
|  | EXPECT_EQ(written_response_id_, storage_delegate_->loaded_info_id_); | 
|  | EXPECT_TRUE(storage_delegate_->loaded_info_.get()); | 
|  | const net::HttpResponseInfo* read_head = | 
|  | storage_delegate_->loaded_info_->http_response_info(); | 
|  | EXPECT_TRUE(read_head); | 
|  | const int metadata_size = strlen(metadata); | 
|  | if (metadata_size) { | 
|  | EXPECT_TRUE(read_head->metadata.get()); | 
|  | EXPECT_EQ(metadata_size, read_head->metadata->size()); | 
|  | EXPECT_EQ(0, | 
|  | memcmp(metadata, read_head->metadata->data(), metadata_size)); | 
|  | } else { | 
|  | EXPECT_FALSE(read_head->metadata.get()); | 
|  | } | 
|  | EXPECT_TRUE(CompareHttpResponseInfos( | 
|  | write_info_buffer_->http_info.get(), | 
|  | storage_delegate_->loaded_info_->http_response_info())); | 
|  | EXPECT_EQ(basic_response_size(), | 
|  | storage_delegate_->loaded_info_->response_data_size()); | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | // AmountWritten ---------------------------------------------------- | 
|  |  | 
|  | void AmountWritten() { | 
|  | static const char kHttpHeaders[] = "HTTP/1.0 200 OK\0\0"; | 
|  | std::string raw_headers(kHttpHeaders, arraysize(kHttpHeaders)); | 
|  | net::HttpResponseInfo* head = MakeHttpResponseInfo(raw_headers); | 
|  | int expected_amount_written = | 
|  | GetHttpResponseInfoSize(head) + kNumBlocks * kBlockSize; | 
|  |  | 
|  | // Push tasks in reverse order. | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::Verify_AmountWritten, | 
|  | base::Unretained(this), expected_amount_written)); | 
|  | for (int i = 0; i < kNumBlocks; ++i) { | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::WriteOneBlock, | 
|  | base::Unretained(this), kNumBlocks - i)); | 
|  | } | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::WriteResponseHead, | 
|  | base::Unretained(this), head)); | 
|  |  | 
|  | writer_.reset(service_->storage()->CreateResponseWriter(GURL())); | 
|  | written_response_id_ = writer_->response_id(); | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void Verify_AmountWritten(int expected_amount_written) { | 
|  | EXPECT_EQ(expected_amount_written, writer_->amount_written()); | 
|  | TestFinished(); | 
|  | } | 
|  |  | 
|  |  | 
|  | // WriteThenVariouslyReadResponse ------------------------------------------- | 
|  |  | 
|  | void WriteThenVariouslyReadResponse() { | 
|  | // This tests involves multiple async steps. | 
|  | // 1. First, write a large body using multiple writes, we don't bother | 
|  | //    with a response head for this test. | 
|  | // 2. Read the entire body, using multiple reads | 
|  | // 3. Read the entire body, using one read. | 
|  | // 4. Attempt to read beyond the EOF. | 
|  | // 5. Read just a range. | 
|  | // 6. Attempt to read beyond EOF of a range. | 
|  |  | 
|  | // Push tasks in reverse order | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::ReadRangeFullyBeyondEOF, | 
|  | base::Unretained(this))); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::ReadRangePartiallyBeyondEOF, | 
|  | base::Unretained(this))); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::ReadPastEOF, | 
|  | base::Unretained(this))); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::ReadRange, | 
|  | base::Unretained(this))); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::ReadPastEOF, | 
|  | base::Unretained(this))); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::ReadAllAtOnce, | 
|  | base::Unretained(this))); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::ReadInBlocks, | 
|  | base::Unretained(this))); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::WriteOutBlocks, | 
|  | base::Unretained(this))); | 
|  |  | 
|  | // Get them going. | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void WriteOutBlocks() { | 
|  | writer_.reset(service_->storage()->CreateResponseWriter(GURL())); | 
|  | written_response_id_ = writer_->response_id(); | 
|  | for (int i = 0; i < kNumBlocks; ++i) { | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::WriteOneBlock, | 
|  | base::Unretained(this), kNumBlocks - i)); | 
|  | } | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void WriteOneBlock(int block_number) { | 
|  | scoped_refptr<IOBuffer> io_buffer( | 
|  | new IOBuffer(kBlockSize)); | 
|  | FillData(block_number, io_buffer->data(), kBlockSize); | 
|  | WriteResponseBody(io_buffer, kBlockSize); | 
|  | } | 
|  |  | 
|  | void ReadInBlocks() { | 
|  | writer_.reset(); | 
|  | reader_.reset(service_->storage()->CreateResponseReader( | 
|  | GURL(), written_response_id_)); | 
|  | for (int i = 0; i < kNumBlocks; ++i) { | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::ReadOneBlock, | 
|  | base::Unretained(this), kNumBlocks - i)); | 
|  | } | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void ReadOneBlock(int block_number) { | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::VerifyOneBlock, | 
|  | base::Unretained(this), block_number)); | 
|  | ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize); | 
|  | } | 
|  |  | 
|  | void VerifyOneBlock(int block_number) { | 
|  | EXPECT_TRUE(CheckData(block_number, read_buffer_->data(), kBlockSize)); | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void ReadAllAtOnce() { | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::VerifyAllAtOnce, | 
|  | base::Unretained(this))); | 
|  | reader_.reset(service_->storage()->CreateResponseReader( | 
|  | GURL(), written_response_id_)); | 
|  | int big_size = kNumBlocks * kBlockSize; | 
|  | ReadResponseBody(new IOBuffer(big_size), big_size); | 
|  | } | 
|  |  | 
|  | void VerifyAllAtOnce() { | 
|  | char* p = read_buffer_->data(); | 
|  | for (int i = 0; i < kNumBlocks; ++i, p += kBlockSize) | 
|  | EXPECT_TRUE(CheckData(i + 1, p, kBlockSize)); | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void ReadPastEOF() { | 
|  | EXPECT_FALSE(reader_->IsReadPending()); | 
|  | read_buffer_ = new IOBuffer(kBlockSize); | 
|  | expected_read_result_ = 0; | 
|  | reader_->ReadData(read_buffer_.get(), | 
|  | kBlockSize, | 
|  | base::Bind(&AppCacheResponseTest::OnReadComplete, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void ReadRange() { | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::VerifyRange, | 
|  | base::Unretained(this))); | 
|  | reader_.reset(service_->storage()->CreateResponseReader( | 
|  | GURL(), written_response_id_)); | 
|  | reader_->SetReadRange(kBlockSize, kBlockSize); | 
|  | ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize); | 
|  | } | 
|  |  | 
|  | void VerifyRange() { | 
|  | EXPECT_TRUE(CheckData(2, read_buffer_->data(), kBlockSize)); | 
|  | ScheduleNextTask();  // ReadPastEOF is scheduled next | 
|  | } | 
|  |  | 
|  | void ReadRangePartiallyBeyondEOF() { | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::VerifyRangeBeyondEOF, | 
|  | base::Unretained(this))); | 
|  | reader_.reset(service_->storage()->CreateResponseReader( | 
|  | GURL(), written_response_id_)); | 
|  | reader_->SetReadRange(kBlockSize, kNumBlocks * kBlockSize); | 
|  | ReadResponseBody(new IOBuffer(kNumBlocks * kBlockSize), | 
|  | kNumBlocks * kBlockSize); | 
|  | expected_read_result_ = (kNumBlocks - 1) * kBlockSize; | 
|  | } | 
|  |  | 
|  | void VerifyRangeBeyondEOF() { | 
|  | // Just verify the first 1k | 
|  | VerifyRange(); | 
|  | } | 
|  |  | 
|  | void ReadRangeFullyBeyondEOF() { | 
|  | reader_.reset(service_->storage()->CreateResponseReader( | 
|  | GURL(), written_response_id_)); | 
|  | reader_->SetReadRange((kNumBlocks * kBlockSize) + 1, kBlockSize); | 
|  | ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize); | 
|  | expected_read_result_ = 0; | 
|  | } | 
|  |  | 
|  | // IOChaining ------------------------------------------- | 
|  | void IOChaining() { | 
|  | // 1. Write several blocks out initiating the subsequent write | 
|  | //    from within the completion callback of the previous write. | 
|  | // 2. Read and verify several blocks in similarly chaining reads. | 
|  |  | 
|  | // Push tasks in reverse order | 
|  | PushNextTaskAsImmediate( | 
|  | base::Bind(&AppCacheResponseTest::ReadInBlocksImmediately, | 
|  | base::Unretained(this))); | 
|  | PushNextTaskAsImmediate( | 
|  | base::Bind(&AppCacheResponseTest::WriteOutBlocksImmediately, | 
|  | base::Unretained(this))); | 
|  |  | 
|  | // Get them going. | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void WriteOutBlocksImmediately() { | 
|  | writer_.reset(service_->storage()->CreateResponseWriter(GURL())); | 
|  | written_response_id_ = writer_->response_id(); | 
|  | for (int i = 0; i < kNumBlocks; ++i) { | 
|  | PushNextTaskAsImmediate( | 
|  | base::Bind(&AppCacheResponseTest::WriteOneBlock, | 
|  | base::Unretained(this), kNumBlocks - i)); | 
|  | } | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void ReadInBlocksImmediately() { | 
|  | writer_.reset(); | 
|  | reader_.reset(service_->storage()->CreateResponseReader( | 
|  | GURL(), written_response_id_)); | 
|  | for (int i = 0; i < kNumBlocks; ++i) { | 
|  | PushNextTaskAsImmediate( | 
|  | base::Bind(&AppCacheResponseTest::ReadOneBlockImmediately, | 
|  | base::Unretained(this), | 
|  | kNumBlocks - i)); | 
|  | } | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void ReadOneBlockImmediately(int block_number) { | 
|  | PushNextTaskAsImmediate(base::Bind(&AppCacheResponseTest::VerifyOneBlock, | 
|  | base::Unretained(this), block_number)); | 
|  | ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize); | 
|  | } | 
|  |  | 
|  | // DeleteWithinCallbacks ------------------------------------------- | 
|  | void DeleteWithinCallbacks() { | 
|  | // 1. Write out a few blocks normally, and upon | 
|  | //    completion of the last write, delete the writer. | 
|  | // 2. Read in a few blocks normally, and upon completion | 
|  | //    of the last read, delete the reader. | 
|  |  | 
|  | should_delete_reader_in_completion_callback_ = true; | 
|  | reader_deletion_count_down_ = kNumBlocks; | 
|  | should_delete_writer_in_completion_callback_ = true; | 
|  | writer_deletion_count_down_ = kNumBlocks; | 
|  |  | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::ReadInBlocks, | 
|  | base::Unretained(this))); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::WriteOutBlocks, | 
|  | base::Unretained(this))); | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | // DeleteWithIOPending ------------------------------------------- | 
|  | void DeleteWithIOPending() { | 
|  | // 1. Write a few blocks normally. | 
|  | // 2. Start a write, delete with it pending. | 
|  | // 3. Start a read, delete with it pending. | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::ReadThenDelete, | 
|  | base::Unretained(this))); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::WriteThenDelete, | 
|  | base::Unretained(this))); | 
|  | PushNextTask(base::Bind(&AppCacheResponseTest::WriteOutBlocks, | 
|  | base::Unretained(this))); | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void WriteThenDelete() { | 
|  | write_callback_was_called_ = false; | 
|  | WriteOneBlock(5); | 
|  | EXPECT_TRUE(writer_->IsWritePending()); | 
|  | writer_.reset(); | 
|  | ScheduleNextTask(); | 
|  | } | 
|  |  | 
|  | void ReadThenDelete() { | 
|  | read_callback_was_called_ = false; | 
|  | reader_.reset(service_->storage()->CreateResponseReader( | 
|  | GURL(), written_response_id_)); | 
|  | ReadResponseBody(new IOBuffer(kBlockSize), kBlockSize); | 
|  | EXPECT_TRUE(reader_->IsReadPending()); | 
|  | reader_.reset(); | 
|  |  | 
|  | // Wait a moment to verify no callbacks. | 
|  | base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | 
|  | FROM_HERE, base::Bind(&AppCacheResponseTest::VerifyNoCallbacks, | 
|  | base::Unretained(this)), | 
|  | base::TimeDelta::FromMilliseconds(10)); | 
|  | } | 
|  |  | 
|  | void VerifyNoCallbacks() { | 
|  | EXPECT_TRUE(!write_callback_was_called_); | 
|  | EXPECT_TRUE(!read_callback_was_called_); | 
|  | TestFinished(); | 
|  | } | 
|  |  | 
|  | // Data members | 
|  |  | 
|  | std::unique_ptr<base::WaitableEvent> test_finished_event_; | 
|  | std::unique_ptr<MockStorageDelegate> storage_delegate_; | 
|  | std::unique_ptr<MockAppCacheService> service_; | 
|  | std::stack<std::pair<base::Closure, bool> > task_stack_; | 
|  |  | 
|  | std::unique_ptr<AppCacheResponseReader> reader_; | 
|  | scoped_refptr<HttpResponseInfoIOBuffer> read_info_buffer_; | 
|  | scoped_refptr<IOBuffer> read_buffer_; | 
|  | int expected_read_result_; | 
|  | bool should_delete_reader_in_completion_callback_; | 
|  | int reader_deletion_count_down_; | 
|  | bool read_callback_was_called_; | 
|  |  | 
|  | int64_t written_response_id_; | 
|  | std::unique_ptr<AppCacheResponseWriter> writer_; | 
|  | std::unique_ptr<AppCacheResponseMetadataWriter> metadata_writer_; | 
|  | scoped_refptr<HttpResponseInfoIOBuffer> write_info_buffer_; | 
|  | scoped_refptr<IOBuffer> write_buffer_; | 
|  | int expected_write_result_; | 
|  | bool should_delete_writer_in_completion_callback_; | 
|  | int writer_deletion_count_down_; | 
|  | bool write_callback_was_called_; | 
|  |  | 
|  | static std::unique_ptr<base::Thread> io_thread_; | 
|  | }; | 
|  |  | 
|  | // static | 
|  | std::unique_ptr<base::Thread> AppCacheResponseTest::io_thread_; | 
|  |  | 
|  | TEST_F(AppCacheResponseTest, ReadNonExistentResponse) { | 
|  | RunTestOnIOThread(&AppCacheResponseTest::ReadNonExistentResponse); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheResponseTest, LoadResponseInfo_Miss) { | 
|  | RunTestOnIOThread(&AppCacheResponseTest::LoadResponseInfo_Miss); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheResponseTest, LoadResponseInfo_Hit) { | 
|  | RunTestOnIOThread(&AppCacheResponseTest::LoadResponseInfo_Hit); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheResponseTest, Metadata) { | 
|  | RunTestOnIOThread(&AppCacheResponseTest::Metadata); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheResponseTest, AmountWritten) { | 
|  | RunTestOnIOThread(&AppCacheResponseTest::AmountWritten); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheResponseTest, WriteThenVariouslyReadResponse) { | 
|  | RunTestOnIOThread(&AppCacheResponseTest::WriteThenVariouslyReadResponse); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheResponseTest, IOChaining) { | 
|  | RunTestOnIOThread(&AppCacheResponseTest::IOChaining); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheResponseTest, DeleteWithinCallbacks) { | 
|  | RunTestOnIOThread(&AppCacheResponseTest::DeleteWithinCallbacks); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheResponseTest, DeleteWithIOPending) { | 
|  | RunTestOnIOThread(&AppCacheResponseTest::DeleteWithIOPending); | 
|  | } | 
|  |  | 
|  | }  // namespace content |