| // 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 "content/browser/cache_storage/cache_storage_cache.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <utility> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_split.h" |
| #include "base/thread_task_runner_handle.h" |
| #include "content/browser/fileapi/chrome_blob_storage_context.h" |
| #include "content/browser/fileapi/mock_url_request_delegate.h" |
| #include "content/browser/quota/mock_quota_manager_proxy.h" |
| #include "content/common/cache_storage/cache_storage_types.h" |
| #include "content/common/service_worker/service_worker_types.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/common/referrer.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_context_getter.h" |
| #include "net/url_request/url_request_job_factory_impl.h" |
| #include "storage/browser/blob/blob_data_builder.h" |
| #include "storage/browser/blob/blob_data_handle.h" |
| #include "storage/browser/blob/blob_data_snapshot.h" |
| #include "storage/browser/blob/blob_storage_context.h" |
| #include "storage/browser/blob/blob_url_request_job_factory.h" |
| #include "storage/browser/quota/quota_manager_proxy.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace content { |
| |
| namespace { |
| const char kTestData[] = "Hello World"; |
| |
| // Returns a BlobProtocolHandler that uses |blob_storage_context|. Caller owns |
| // the memory. |
| scoped_ptr<storage::BlobProtocolHandler> CreateMockBlobProtocolHandler( |
| storage::BlobStorageContext* blob_storage_context) { |
| // The FileSystemContext and thread task runner are not actually used but a |
| // task runner is needed to avoid a DCHECK in BlobURLRequestJob ctor. |
| return make_scoped_ptr(new storage::BlobProtocolHandler( |
| blob_storage_context, NULL, base::ThreadTaskRunnerHandle::Get().get())); |
| } |
| |
| // A disk_cache::Backend wrapper that can delay operations. |
| class DelayableBackend : public disk_cache::Backend { |
| public: |
| DelayableBackend(scoped_ptr<disk_cache::Backend> backend) |
| : backend_(std::move(backend)), delay_open_(false) {} |
| |
| // disk_cache::Backend overrides |
| net::CacheType GetCacheType() const override { |
| return backend_->GetCacheType(); |
| } |
| int32_t GetEntryCount() const override { return backend_->GetEntryCount(); } |
| int OpenEntry(const std::string& key, |
| disk_cache::Entry** entry, |
| const CompletionCallback& callback) override { |
| if (delay_open_) { |
| open_entry_callback_ = |
| base::Bind(&DelayableBackend::OpenEntryDelayedImpl, |
| base::Unretained(this), key, entry, callback); |
| return net::ERR_IO_PENDING; |
| } |
| |
| return backend_->OpenEntry(key, entry, callback); |
| } |
| int CreateEntry(const std::string& key, |
| disk_cache::Entry** entry, |
| const CompletionCallback& callback) override { |
| return backend_->CreateEntry(key, entry, callback); |
| } |
| int DoomEntry(const std::string& key, |
| const CompletionCallback& callback) override { |
| return backend_->DoomEntry(key, callback); |
| } |
| int DoomAllEntries(const CompletionCallback& callback) override { |
| return backend_->DoomAllEntries(callback); |
| } |
| int DoomEntriesBetween(base::Time initial_time, |
| base::Time end_time, |
| const CompletionCallback& callback) override { |
| return backend_->DoomEntriesBetween(initial_time, end_time, callback); |
| } |
| int DoomEntriesSince(base::Time initial_time, |
| const CompletionCallback& callback) override { |
| return backend_->DoomEntriesSince(initial_time, callback); |
| } |
| int CalculateSizeOfAllEntries( |
| const CompletionCallback& callback) override { |
| return backend_->CalculateSizeOfAllEntries(callback); |
| } |
| scoped_ptr<Iterator> CreateIterator() override { |
| return backend_->CreateIterator(); |
| } |
| void GetStats(base::StringPairs* stats) override { |
| return backend_->GetStats(stats); |
| } |
| void OnExternalCacheHit(const std::string& key) override { |
| return backend_->OnExternalCacheHit(key); |
| } |
| |
| // Call to continue a delayed open. |
| void OpenEntryContinue() { |
| EXPECT_FALSE(open_entry_callback_.is_null()); |
| open_entry_callback_.Run(); |
| } |
| |
| void set_delay_open(bool value) { delay_open_ = value; } |
| |
| private: |
| void OpenEntryDelayedImpl(const std::string& key, |
| disk_cache::Entry** entry, |
| const CompletionCallback& callback) { |
| int rv = backend_->OpenEntry(key, entry, callback); |
| if (rv != net::ERR_IO_PENDING) |
| callback.Run(rv); |
| } |
| |
| scoped_ptr<disk_cache::Backend> backend_; |
| bool delay_open_; |
| base::Closure open_entry_callback_; |
| }; |
| |
| void CopyBody(const storage::BlobDataHandle& blob_handle, std::string* output) { |
| *output = std::string(); |
| scoped_ptr<storage::BlobDataSnapshot> data = blob_handle.CreateSnapshot(); |
| const auto& items = data->items(); |
| for (const auto& item : items) { |
| switch (item->type()) { |
| case storage::DataElement::TYPE_BYTES: { |
| output->append(item->bytes(), item->length()); |
| break; |
| } |
| case storage::DataElement::TYPE_DISK_CACHE_ENTRY: { |
| disk_cache::Entry* entry = item->disk_cache_entry(); |
| int32_t body_size = entry->GetDataSize(item->disk_cache_stream_index()); |
| |
| scoped_refptr<net::IOBuffer> io_buffer = new net::IOBuffer(body_size); |
| net::TestCompletionCallback callback; |
| int rv = |
| entry->ReadData(item->disk_cache_stream_index(), 0, io_buffer.get(), |
| body_size, callback.callback()); |
| if (rv == net::ERR_IO_PENDING) |
| rv = callback.WaitForResult(); |
| EXPECT_EQ(body_size, rv); |
| if (rv > 0) |
| output->append(io_buffer->data(), rv); |
| break; |
| } |
| default: { ADD_FAILURE() << "invalid response blob type"; } break; |
| } |
| } |
| } |
| |
| bool ResponseMetadataEqual(const ServiceWorkerResponse& expected, |
| const ServiceWorkerResponse& actual) { |
| EXPECT_EQ(expected.status_code, actual.status_code); |
| if (expected.status_code != actual.status_code) |
| return false; |
| EXPECT_EQ(expected.status_text, actual.status_text); |
| if (expected.status_text != actual.status_text) |
| return false; |
| EXPECT_EQ(expected.url, actual.url); |
| if (expected.url != actual.url) |
| return false; |
| EXPECT_EQ(expected.blob_size, actual.blob_size); |
| if (expected.blob_size != actual.blob_size) |
| return false; |
| |
| if (expected.blob_size == 0) { |
| EXPECT_STREQ("", actual.blob_uuid.c_str()); |
| if (!actual.blob_uuid.empty()) |
| return false; |
| } else { |
| EXPECT_STRNE("", actual.blob_uuid.c_str()); |
| if (actual.blob_uuid.empty()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ResponseBodiesEqual(const std::string& expected_body, |
| const storage::BlobDataHandle& actual_body_handle) { |
| std::string actual_body; |
| CopyBody(actual_body_handle, &actual_body); |
| return expected_body == actual_body; |
| } |
| |
| } // namespace |
| |
| // A CacheStorageCache that can optionally delay during backend creation. |
| class TestCacheStorageCache : public CacheStorageCache { |
| public: |
| TestCacheStorageCache( |
| const GURL& origin, |
| const base::FilePath& path, |
| const scoped_refptr<net::URLRequestContextGetter>& request_context_getter, |
| const scoped_refptr<storage::QuotaManagerProxy>& quota_manager_proxy, |
| base::WeakPtr<storage::BlobStorageContext> blob_context) |
| : CacheStorageCache(origin, |
| path, |
| request_context_getter, |
| quota_manager_proxy, |
| blob_context), |
| delay_backend_creation_(false) {} |
| |
| void CreateBackend(const ErrorCallback& callback) override { |
| backend_creation_callback_ = callback; |
| if (delay_backend_creation_) |
| return; |
| ContinueCreateBackend(); |
| } |
| |
| void ContinueCreateBackend() { |
| CacheStorageCache::CreateBackend(backend_creation_callback_); |
| } |
| |
| void set_delay_backend_creation(bool delay) { |
| delay_backend_creation_ = delay; |
| } |
| |
| // Swap the existing backend with a delayable one. The backend must have been |
| // created before calling this. |
| DelayableBackend* UseDelayableBackend() { |
| EXPECT_TRUE(backend_); |
| DelayableBackend* delayable_backend = |
| new DelayableBackend(std::move(backend_)); |
| backend_.reset(delayable_backend); |
| return delayable_backend; |
| } |
| |
| private: |
| ~TestCacheStorageCache() override {} |
| |
| bool delay_backend_creation_; |
| ErrorCallback backend_creation_callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestCacheStorageCache); |
| }; |
| |
| class CacheStorageCacheTest : public testing::Test { |
| public: |
| CacheStorageCacheTest() |
| : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), |
| callback_error_(CACHE_STORAGE_OK), |
| callback_closed_(false) {} |
| |
| void SetUp() override { |
| ChromeBlobStorageContext* blob_storage_context = |
| ChromeBlobStorageContext::GetFor(&browser_context_); |
| // Wait for chrome_blob_storage_context to finish initializing. |
| base::RunLoop().RunUntilIdle(); |
| blob_storage_context_ = blob_storage_context->context(); |
| |
| quota_manager_proxy_ = new MockQuotaManagerProxy( |
| nullptr, base::ThreadTaskRunnerHandle::Get().get()); |
| |
| url_request_job_factory_.reset(new net::URLRequestJobFactoryImpl); |
| url_request_job_factory_->SetProtocolHandler( |
| "blob", CreateMockBlobProtocolHandler(blob_storage_context->context())); |
| |
| net::URLRequestContext* url_request_context = |
| browser_context_.GetRequestContext()->GetURLRequestContext(); |
| |
| url_request_context->set_job_factory(url_request_job_factory_.get()); |
| |
| CreateRequests(blob_storage_context); |
| |
| if (!MemoryOnly()) |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| base::FilePath path = MemoryOnly() ? base::FilePath() : temp_dir_.path(); |
| |
| cache_ = make_scoped_refptr(new TestCacheStorageCache( |
| GURL("http://example.com"), path, browser_context_.GetRequestContext(), |
| quota_manager_proxy_, blob_storage_context->context()->AsWeakPtr())); |
| } |
| |
| void TearDown() override { |
| quota_manager_proxy_->SimulateQuotaManagerDestroyed(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void CreateRequests(ChromeBlobStorageContext* blob_storage_context) { |
| ServiceWorkerHeaderMap headers; |
| headers.insert(std::make_pair("a", "a")); |
| headers.insert(std::make_pair("b", "b")); |
| body_request_ = |
| ServiceWorkerFetchRequest(GURL("http://example.com/body.html"), "GET", |
| headers, Referrer(), false); |
| no_body_request_ = |
| ServiceWorkerFetchRequest(GURL("http://example.com/no_body.html"), |
| "GET", headers, Referrer(), false); |
| |
| std::string expected_response; |
| for (int i = 0; i < 100; ++i) |
| expected_blob_data_ += kTestData; |
| |
| scoped_ptr<storage::BlobDataBuilder> blob_data( |
| new storage::BlobDataBuilder("blob-id:myblob")); |
| blob_data->AppendData(expected_blob_data_); |
| |
| blob_handle_ = |
| blob_storage_context->context()->AddFinishedBlob(blob_data.get()); |
| |
| body_response_ = ServiceWorkerResponse( |
| GURL("http://example.com/body.html"), 200, "OK", |
| blink::WebServiceWorkerResponseTypeDefault, headers, |
| blob_handle_->uuid(), expected_blob_data_.size(), GURL(), |
| blink::WebServiceWorkerResponseErrorUnknown); |
| |
| no_body_response_ = ServiceWorkerResponse( |
| GURL("http://example.com/no_body.html"), 200, "OK", |
| blink::WebServiceWorkerResponseTypeDefault, headers, "", 0, GURL(), |
| blink::WebServiceWorkerResponseErrorUnknown); |
| } |
| |
| scoped_ptr<ServiceWorkerFetchRequest> CopyFetchRequest( |
| const ServiceWorkerFetchRequest& request) { |
| return make_scoped_ptr(new ServiceWorkerFetchRequest( |
| request.url, request.method, request.headers, request.referrer, |
| request.is_reload)); |
| } |
| |
| CacheStorageError BatchOperation( |
| const std::vector<CacheStorageBatchOperation>& operations) { |
| scoped_ptr<base::RunLoop> loop(new base::RunLoop()); |
| |
| cache_->BatchOperation( |
| operations, |
| base::Bind(&CacheStorageCacheTest::ErrorTypeCallback, |
| base::Unretained(this), base::Unretained(loop.get()))); |
| // TODO(jkarlin): These functions should use base::RunLoop().RunUntilIdle() |
| // once the cache uses a passed in task runner instead of the CACHE thread. |
| loop->Run(); |
| |
| return callback_error_; |
| } |
| |
| bool Put(const ServiceWorkerFetchRequest& request, |
| const ServiceWorkerResponse& response) { |
| CacheStorageBatchOperation operation; |
| operation.operation_type = CACHE_STORAGE_CACHE_OPERATION_TYPE_PUT; |
| operation.request = request; |
| operation.response = response; |
| |
| CacheStorageError error = |
| BatchOperation(std::vector<CacheStorageBatchOperation>(1, operation)); |
| return error == CACHE_STORAGE_OK; |
| } |
| |
| bool Match(const ServiceWorkerFetchRequest& request) { |
| scoped_ptr<base::RunLoop> loop(new base::RunLoop()); |
| |
| cache_->Match( |
| CopyFetchRequest(request), |
| base::Bind(&CacheStorageCacheTest::ResponseAndErrorCallback, |
| base::Unretained(this), base::Unretained(loop.get()))); |
| loop->Run(); |
| |
| return callback_error_ == CACHE_STORAGE_OK; |
| } |
| |
| bool MatchAll(scoped_ptr<CacheStorageCache::Responses>* responses, |
| scoped_ptr<CacheStorageCache::BlobDataHandles>* body_handles) { |
| base::RunLoop loop; |
| cache_->MatchAll(base::Bind( |
| &CacheStorageCacheTest::ResponsesAndErrorCallback, |
| base::Unretained(this), loop.QuitClosure(), responses, body_handles)); |
| loop.Run(); |
| return callback_error_ == CACHE_STORAGE_OK; |
| } |
| |
| bool Delete(const ServiceWorkerFetchRequest& request) { |
| CacheStorageBatchOperation operation; |
| operation.operation_type = CACHE_STORAGE_CACHE_OPERATION_TYPE_DELETE; |
| operation.request = request; |
| |
| CacheStorageError error = |
| BatchOperation(std::vector<CacheStorageBatchOperation>(1, operation)); |
| return error == CACHE_STORAGE_OK; |
| } |
| |
| bool Keys() { |
| scoped_ptr<base::RunLoop> loop(new base::RunLoop()); |
| |
| cache_->Keys(base::Bind(&CacheStorageCacheTest::RequestsCallback, |
| base::Unretained(this), |
| base::Unretained(loop.get()))); |
| loop->Run(); |
| |
| return callback_error_ == CACHE_STORAGE_OK; |
| } |
| |
| bool Close() { |
| scoped_ptr<base::RunLoop> loop(new base::RunLoop()); |
| |
| cache_->Close(base::Bind(&CacheStorageCacheTest::CloseCallback, |
| base::Unretained(this), |
| base::Unretained(loop.get()))); |
| loop->Run(); |
| return callback_closed_; |
| } |
| |
| void RequestsCallback(base::RunLoop* run_loop, |
| CacheStorageError error, |
| scoped_ptr<CacheStorageCache::Requests> requests) { |
| callback_error_ = error; |
| callback_strings_.clear(); |
| if (requests) { |
| for (size_t i = 0u; i < requests->size(); ++i) |
| callback_strings_.push_back(requests->at(i).url.spec()); |
| } |
| if (run_loop) |
| run_loop->Quit(); |
| } |
| |
| void ErrorTypeCallback(base::RunLoop* run_loop, CacheStorageError error) { |
| callback_error_ = error; |
| if (run_loop) |
| run_loop->Quit(); |
| } |
| |
| void SequenceCallback(int sequence, |
| int* sequence_out, |
| base::RunLoop* run_loop, |
| CacheStorageError error) { |
| *sequence_out = sequence; |
| callback_error_ = error; |
| if (run_loop) |
| run_loop->Quit(); |
| } |
| |
| void ResponseAndErrorCallback( |
| base::RunLoop* run_loop, |
| CacheStorageError error, |
| scoped_ptr<ServiceWorkerResponse> response, |
| scoped_ptr<storage::BlobDataHandle> body_handle) { |
| callback_error_ = error; |
| callback_response_ = std::move(response); |
| callback_response_data_.reset(); |
| if (error == CACHE_STORAGE_OK && !callback_response_->blob_uuid.empty()) |
| callback_response_data_ = std::move(body_handle); |
| |
| if (run_loop) |
| run_loop->Quit(); |
| } |
| |
| void ResponsesAndErrorCallback( |
| const base::Closure& quit_closure, |
| scoped_ptr<CacheStorageCache::Responses>* responses_out, |
| scoped_ptr<CacheStorageCache::BlobDataHandles>* body_handles_out, |
| CacheStorageError error, |
| scoped_ptr<CacheStorageCache::Responses> responses, |
| scoped_ptr<CacheStorageCache::BlobDataHandles> body_handles) { |
| callback_error_ = error; |
| responses_out->swap(responses); |
| body_handles_out->swap(body_handles); |
| quit_closure.Run(); |
| } |
| |
| void CloseCallback(base::RunLoop* run_loop) { |
| EXPECT_FALSE(callback_closed_); |
| callback_closed_ = true; |
| if (run_loop) |
| run_loop->Quit(); |
| } |
| |
| bool VerifyKeys(const std::vector<std::string>& expected_keys) { |
| if (expected_keys.size() != callback_strings_.size()) |
| return false; |
| |
| std::set<std::string> found_set; |
| for (int i = 0, max = callback_strings_.size(); i < max; ++i) |
| found_set.insert(callback_strings_[i]); |
| |
| for (int i = 0, max = expected_keys.size(); i < max; ++i) { |
| if (found_set.find(expected_keys[i]) == found_set.end()) |
| return false; |
| } |
| return true; |
| } |
| |
| bool TestResponseType(blink::WebServiceWorkerResponseType response_type) { |
| body_response_.response_type = response_type; |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_TRUE(Match(body_request_)); |
| EXPECT_TRUE(Delete(body_request_)); |
| return response_type == callback_response_->response_type; |
| } |
| |
| void VerifyAllOpsFail() { |
| EXPECT_FALSE(Put(no_body_request_, no_body_response_)); |
| EXPECT_FALSE(Match(no_body_request_)); |
| EXPECT_FALSE(Delete(body_request_)); |
| EXPECT_FALSE(Keys()); |
| } |
| |
| virtual bool MemoryOnly() { return false; } |
| |
| protected: |
| base::ScopedTempDir temp_dir_; |
| TestBrowserContext browser_context_; |
| TestBrowserThreadBundle browser_thread_bundle_; |
| scoped_ptr<net::URLRequestJobFactoryImpl> url_request_job_factory_; |
| scoped_refptr<MockQuotaManagerProxy> quota_manager_proxy_; |
| storage::BlobStorageContext* blob_storage_context_; |
| |
| scoped_refptr<TestCacheStorageCache> cache_; |
| |
| ServiceWorkerFetchRequest body_request_; |
| ServiceWorkerResponse body_response_; |
| ServiceWorkerFetchRequest no_body_request_; |
| ServiceWorkerResponse no_body_response_; |
| scoped_ptr<storage::BlobDataHandle> blob_handle_; |
| std::string expected_blob_data_; |
| |
| CacheStorageError callback_error_; |
| scoped_ptr<ServiceWorkerResponse> callback_response_; |
| scoped_ptr<storage::BlobDataHandle> callback_response_data_; |
| std::vector<std::string> callback_strings_; |
| bool callback_closed_; |
| }; |
| |
| class CacheStorageCacheTestP : public CacheStorageCacheTest, |
| public testing::WithParamInterface<bool> { |
| bool MemoryOnly() override { return !GetParam(); } |
| }; |
| |
| class CacheStorageCacheMemoryOnlyTest |
| : public CacheStorageCacheTest, |
| public testing::WithParamInterface<bool> { |
| bool MemoryOnly() override { return true; } |
| }; |
| |
| TEST_P(CacheStorageCacheTestP, PutNoBody) { |
| EXPECT_TRUE(Put(no_body_request_, no_body_response_)); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, PutBody) { |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, PutBody_Multiple) { |
| CacheStorageBatchOperation operation1; |
| operation1.operation_type = CACHE_STORAGE_CACHE_OPERATION_TYPE_PUT; |
| operation1.request = body_request_; |
| operation1.request.url = GURL("http://example.com/1"); |
| operation1.response = body_response_; |
| operation1.response.url = GURL("http://example.com/1"); |
| |
| CacheStorageBatchOperation operation2; |
| operation2.operation_type = CACHE_STORAGE_CACHE_OPERATION_TYPE_PUT; |
| operation2.request = body_request_; |
| operation2.request.url = GURL("http://example.com/2"); |
| operation2.response = body_response_; |
| operation2.response.url = GURL("http://example.com/2"); |
| |
| CacheStorageBatchOperation operation3; |
| operation3.operation_type = CACHE_STORAGE_CACHE_OPERATION_TYPE_PUT; |
| operation3.request = body_request_; |
| operation3.request.url = GURL("http://example.com/3"); |
| operation3.response = body_response_; |
| operation3.response.url = GURL("http://example.com/3"); |
| |
| std::vector<CacheStorageBatchOperation> operations; |
| operations.push_back(operation1); |
| operations.push_back(operation2); |
| operations.push_back(operation3); |
| |
| EXPECT_EQ(CACHE_STORAGE_OK, BatchOperation(operations)); |
| EXPECT_TRUE(Match(operation1.request)); |
| EXPECT_TRUE(Match(operation2.request)); |
| EXPECT_TRUE(Match(operation3.request)); |
| } |
| |
| // TODO(nhiroki): Add a test for the case where one of PUT operations fails. |
| // Currently there is no handy way to fail only one operation in a batch. |
| // This could be easily achieved after adding some security checks in the |
| // browser side (http://crbug.com/425505). |
| |
| TEST_P(CacheStorageCacheTestP, ResponseURLDiffersFromRequestURL) { |
| no_body_response_.url = GURL("http://example.com/foobar"); |
| EXPECT_STRNE("http://example.com/foobar", |
| no_body_request_.url.spec().c_str()); |
| EXPECT_TRUE(Put(no_body_request_, no_body_response_)); |
| EXPECT_TRUE(Match(no_body_request_)); |
| EXPECT_STREQ("http://example.com/foobar", |
| callback_response_->url.spec().c_str()); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, ResponseURLEmpty) { |
| no_body_response_.url = GURL(); |
| EXPECT_STRNE("", no_body_request_.url.spec().c_str()); |
| EXPECT_TRUE(Put(no_body_request_, no_body_response_)); |
| EXPECT_TRUE(Match(no_body_request_)); |
| EXPECT_STREQ("", callback_response_->url.spec().c_str()); |
| } |
| |
| TEST_F(CacheStorageCacheTest, PutBodyDropBlobRef) { |
| CacheStorageBatchOperation operation; |
| operation.operation_type = CACHE_STORAGE_CACHE_OPERATION_TYPE_PUT; |
| operation.request = body_request_; |
| operation.response = body_response_; |
| |
| scoped_ptr<base::RunLoop> loop(new base::RunLoop()); |
| cache_->BatchOperation( |
| std::vector<CacheStorageBatchOperation>(1, operation), |
| base::Bind(&CacheStorageCacheTestP::ErrorTypeCallback, |
| base::Unretained(this), base::Unretained(loop.get()))); |
| // The handle should be held by the cache now so the deref here should be |
| // okay. |
| blob_handle_.reset(); |
| loop->Run(); |
| |
| EXPECT_EQ(CACHE_STORAGE_OK, callback_error_); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, PutReplace) { |
| EXPECT_TRUE(Put(body_request_, no_body_response_)); |
| EXPECT_TRUE(Match(body_request_)); |
| EXPECT_FALSE(callback_response_data_); |
| |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_TRUE(Match(body_request_)); |
| EXPECT_TRUE(callback_response_data_); |
| |
| EXPECT_TRUE(Put(body_request_, no_body_response_)); |
| EXPECT_TRUE(Match(body_request_)); |
| EXPECT_FALSE(callback_response_data_); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, PutReplcaceInBatch) { |
| CacheStorageBatchOperation operation1; |
| operation1.operation_type = CACHE_STORAGE_CACHE_OPERATION_TYPE_PUT; |
| operation1.request = body_request_; |
| operation1.response = no_body_response_; |
| |
| CacheStorageBatchOperation operation2; |
| operation2.operation_type = CACHE_STORAGE_CACHE_OPERATION_TYPE_PUT; |
| operation2.request = body_request_; |
| operation2.response = body_response_; |
| |
| std::vector<CacheStorageBatchOperation> operations; |
| operations.push_back(operation1); |
| operations.push_back(operation2); |
| |
| EXPECT_EQ(CACHE_STORAGE_OK, BatchOperation(operations)); |
| |
| // |operation2| should win. |
| EXPECT_TRUE(Match(operation2.request)); |
| EXPECT_TRUE(callback_response_data_); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, MatchNoBody) { |
| EXPECT_TRUE(Put(no_body_request_, no_body_response_)); |
| EXPECT_TRUE(Match(no_body_request_)); |
| EXPECT_TRUE(ResponseMetadataEqual(no_body_response_, *callback_response_)); |
| EXPECT_FALSE(callback_response_data_); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, MatchBody) { |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_TRUE(Match(body_request_)); |
| EXPECT_TRUE(ResponseMetadataEqual(body_response_, *callback_response_)); |
| EXPECT_TRUE( |
| ResponseBodiesEqual(expected_blob_data_, *callback_response_data_)); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, MatchAll_Empty) { |
| scoped_ptr<CacheStorageCache::Responses> responses; |
| scoped_ptr<CacheStorageCache::BlobDataHandles> body_handles; |
| EXPECT_TRUE(MatchAll(&responses, &body_handles)); |
| EXPECT_TRUE(responses->empty()); |
| EXPECT_TRUE(body_handles->empty()); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, MatchAll_NoBody) { |
| EXPECT_TRUE(Put(no_body_request_, no_body_response_)); |
| |
| scoped_ptr<CacheStorageCache::Responses> responses; |
| scoped_ptr<CacheStorageCache::BlobDataHandles> body_handles; |
| EXPECT_TRUE(MatchAll(&responses, &body_handles)); |
| |
| ASSERT_EQ(1u, responses->size()); |
| EXPECT_TRUE(ResponseMetadataEqual(no_body_response_, responses->at(0))); |
| EXPECT_TRUE(body_handles->empty()); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, MatchAll_Body) { |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| |
| scoped_ptr<CacheStorageCache::Responses> responses; |
| scoped_ptr<CacheStorageCache::BlobDataHandles> body_handles; |
| EXPECT_TRUE(MatchAll(&responses, &body_handles)); |
| |
| ASSERT_EQ(1u, responses->size()); |
| ASSERT_EQ(1u, body_handles->size()); |
| EXPECT_TRUE(ResponseMetadataEqual(body_response_, responses->at(0))); |
| EXPECT_TRUE(ResponseBodiesEqual(expected_blob_data_, body_handles->at(0))); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, MatchAll_TwoResponsesThenOne) { |
| EXPECT_TRUE(Put(no_body_request_, no_body_response_)); |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| |
| scoped_ptr<CacheStorageCache::Responses> responses; |
| scoped_ptr<CacheStorageCache::BlobDataHandles> body_handles; |
| EXPECT_TRUE(MatchAll(&responses, &body_handles)); |
| ASSERT_EQ(2u, responses->size()); |
| ASSERT_EQ(1u, body_handles->size()); |
| |
| // Order of returned responses is not guaranteed. |
| std::set<std::string> matched_set; |
| for (const ServiceWorkerResponse& response : *responses) { |
| if (response.url.spec() == "http://example.com/no_body.html") { |
| EXPECT_TRUE(ResponseMetadataEqual(no_body_response_, response)); |
| matched_set.insert(response.url.spec()); |
| } else if (response.url.spec() == "http://example.com/body.html") { |
| EXPECT_TRUE(ResponseMetadataEqual(body_response_, response)); |
| EXPECT_TRUE( |
| ResponseBodiesEqual(expected_blob_data_, body_handles->at(0))); |
| matched_set.insert(response.url.spec()); |
| } |
| } |
| EXPECT_EQ(2u, matched_set.size()); |
| |
| responses->clear(); |
| body_handles->clear(); |
| |
| EXPECT_TRUE(Delete(body_request_)); |
| EXPECT_TRUE(MatchAll(&responses, &body_handles)); |
| |
| ASSERT_EQ(1u, responses->size()); |
| EXPECT_TRUE(ResponseMetadataEqual(no_body_response_, responses->at(0))); |
| EXPECT_TRUE(body_handles->empty()); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, Vary) { |
| body_request_.headers["vary_foo"] = "foo"; |
| body_response_.headers["vary"] = "vary_foo"; |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_TRUE(Match(body_request_)); |
| |
| body_request_.headers["vary_foo"] = "bar"; |
| EXPECT_FALSE(Match(body_request_)); |
| |
| body_request_.headers.erase("vary_foo"); |
| EXPECT_FALSE(Match(body_request_)); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, EmptyVary) { |
| body_response_.headers["vary"] = ""; |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_TRUE(Match(body_request_)); |
| |
| body_request_.headers["zoo"] = "zoo"; |
| EXPECT_TRUE(Match(body_request_)); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, NoVaryButDiffHeaders) { |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_TRUE(Match(body_request_)); |
| |
| body_request_.headers["zoo"] = "zoo"; |
| EXPECT_TRUE(Match(body_request_)); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, VaryMultiple) { |
| body_request_.headers["vary_foo"] = "foo"; |
| body_request_.headers["vary_bar"] = "bar"; |
| body_response_.headers["vary"] = " vary_foo , vary_bar"; |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_TRUE(Match(body_request_)); |
| |
| body_request_.headers["vary_bar"] = "foo"; |
| EXPECT_FALSE(Match(body_request_)); |
| |
| body_request_.headers.erase("vary_bar"); |
| EXPECT_FALSE(Match(body_request_)); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, VaryNewHeader) { |
| body_request_.headers["vary_foo"] = "foo"; |
| body_response_.headers["vary"] = " vary_foo, vary_bar"; |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_TRUE(Match(body_request_)); |
| |
| body_request_.headers["vary_bar"] = "bar"; |
| EXPECT_FALSE(Match(body_request_)); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, VaryStar) { |
| body_response_.headers["vary"] = "*"; |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_FALSE(Match(body_request_)); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, EmptyKeys) { |
| EXPECT_TRUE(Keys()); |
| EXPECT_EQ(0u, callback_strings_.size()); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, TwoKeys) { |
| EXPECT_TRUE(Put(no_body_request_, no_body_response_)); |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_TRUE(Keys()); |
| EXPECT_EQ(2u, callback_strings_.size()); |
| std::vector<std::string> expected_keys; |
| expected_keys.push_back(no_body_request_.url.spec()); |
| expected_keys.push_back(body_request_.url.spec()); |
| EXPECT_TRUE(VerifyKeys(expected_keys)); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, TwoKeysThenOne) { |
| EXPECT_TRUE(Put(no_body_request_, no_body_response_)); |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_TRUE(Keys()); |
| EXPECT_EQ(2u, callback_strings_.size()); |
| std::vector<std::string> expected_keys; |
| expected_keys.push_back(no_body_request_.url.spec()); |
| expected_keys.push_back(body_request_.url.spec()); |
| EXPECT_TRUE(VerifyKeys(expected_keys)); |
| |
| EXPECT_TRUE(Delete(body_request_)); |
| EXPECT_TRUE(Keys()); |
| EXPECT_EQ(1u, callback_strings_.size()); |
| std::vector<std::string> expected_key; |
| expected_key.push_back(no_body_request_.url.spec()); |
| EXPECT_TRUE(VerifyKeys(expected_key)); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, DeleteNoBody) { |
| EXPECT_TRUE(Put(no_body_request_, no_body_response_)); |
| EXPECT_TRUE(Match(no_body_request_)); |
| EXPECT_TRUE(Delete(no_body_request_)); |
| EXPECT_FALSE(Match(no_body_request_)); |
| EXPECT_FALSE(Delete(no_body_request_)); |
| EXPECT_TRUE(Put(no_body_request_, no_body_response_)); |
| EXPECT_TRUE(Match(no_body_request_)); |
| EXPECT_TRUE(Delete(no_body_request_)); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, DeleteBody) { |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_TRUE(Match(body_request_)); |
| EXPECT_TRUE(Delete(body_request_)); |
| EXPECT_FALSE(Match(body_request_)); |
| EXPECT_FALSE(Delete(body_request_)); |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_TRUE(Match(body_request_)); |
| EXPECT_TRUE(Delete(body_request_)); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, QuickStressNoBody) { |
| for (int i = 0; i < 100; ++i) { |
| EXPECT_FALSE(Match(no_body_request_)); |
| EXPECT_TRUE(Put(no_body_request_, no_body_response_)); |
| EXPECT_TRUE(Match(no_body_request_)); |
| EXPECT_TRUE(Delete(no_body_request_)); |
| } |
| } |
| |
| TEST_P(CacheStorageCacheTestP, QuickStressBody) { |
| for (int i = 0; i < 100; ++i) { |
| ASSERT_FALSE(Match(body_request_)); |
| ASSERT_TRUE(Put(body_request_, body_response_)); |
| ASSERT_TRUE(Match(body_request_)); |
| ASSERT_TRUE(Delete(body_request_)); |
| } |
| } |
| |
| TEST_P(CacheStorageCacheTestP, PutResponseType) { |
| EXPECT_TRUE(TestResponseType(blink::WebServiceWorkerResponseTypeBasic)); |
| EXPECT_TRUE(TestResponseType(blink::WebServiceWorkerResponseTypeCORS)); |
| EXPECT_TRUE(TestResponseType(blink::WebServiceWorkerResponseTypeDefault)); |
| EXPECT_TRUE(TestResponseType(blink::WebServiceWorkerResponseTypeError)); |
| EXPECT_TRUE(TestResponseType(blink::WebServiceWorkerResponseTypeOpaque)); |
| } |
| |
| TEST_F(CacheStorageCacheTest, CaselessServiceWorkerResponseHeaders) { |
| // CacheStorageCache depends on ServiceWorkerResponse having caseless |
| // headers so that it can quickly lookup vary headers. |
| ServiceWorkerResponse response(GURL("http://www.example.com"), 200, "OK", |
| blink::WebServiceWorkerResponseTypeDefault, |
| ServiceWorkerHeaderMap(), "", 0, GURL(), |
| blink::WebServiceWorkerResponseErrorUnknown); |
| response.headers["content-type"] = "foo"; |
| response.headers["Content-Type"] = "bar"; |
| EXPECT_EQ("bar", response.headers["content-type"]); |
| } |
| |
| TEST_F(CacheStorageCacheTest, CaselessServiceWorkerFetchRequestHeaders) { |
| // CacheStorageCache depends on ServiceWorkerFetchRequest having caseless |
| // headers so that it can quickly lookup vary headers. |
| ServiceWorkerFetchRequest request(GURL("http://www.example.com"), "GET", |
| ServiceWorkerHeaderMap(), Referrer(), |
| false); |
| request.headers["content-type"] = "foo"; |
| request.headers["Content-Type"] = "bar"; |
| EXPECT_EQ("bar", request.headers["content-type"]); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, QuotaManagerModified) { |
| EXPECT_EQ(0, quota_manager_proxy_->notify_storage_modified_count()); |
| |
| EXPECT_TRUE(Put(no_body_request_, no_body_response_)); |
| EXPECT_EQ(1, quota_manager_proxy_->notify_storage_modified_count()); |
| EXPECT_LT(0, quota_manager_proxy_->last_notified_delta()); |
| int64_t sum_delta = quota_manager_proxy_->last_notified_delta(); |
| |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_EQ(2, quota_manager_proxy_->notify_storage_modified_count()); |
| EXPECT_LT(sum_delta, quota_manager_proxy_->last_notified_delta()); |
| sum_delta += quota_manager_proxy_->last_notified_delta(); |
| |
| EXPECT_TRUE(Delete(body_request_)); |
| EXPECT_EQ(3, quota_manager_proxy_->notify_storage_modified_count()); |
| sum_delta += quota_manager_proxy_->last_notified_delta(); |
| |
| EXPECT_TRUE(Delete(no_body_request_)); |
| EXPECT_EQ(4, quota_manager_proxy_->notify_storage_modified_count()); |
| sum_delta += quota_manager_proxy_->last_notified_delta(); |
| |
| EXPECT_EQ(0, sum_delta); |
| } |
| |
| TEST_F(CacheStorageCacheMemoryOnlyTest, MemoryBackedSize) { |
| EXPECT_EQ(0, cache_->MemoryBackedSize()); |
| EXPECT_TRUE(Put(no_body_request_, no_body_response_)); |
| EXPECT_LT(0, cache_->MemoryBackedSize()); |
| int64_t no_body_size = cache_->MemoryBackedSize(); |
| |
| EXPECT_TRUE(Delete(no_body_request_)); |
| EXPECT_EQ(0, cache_->MemoryBackedSize()); |
| |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_LT(no_body_size, cache_->MemoryBackedSize()); |
| |
| EXPECT_TRUE(Delete(body_request_)); |
| EXPECT_EQ(0, cache_->MemoryBackedSize()); |
| } |
| |
| TEST_F(CacheStorageCacheTest, MemoryBackedSizePersistent) { |
| EXPECT_EQ(0, cache_->MemoryBackedSize()); |
| EXPECT_TRUE(Put(no_body_request_, no_body_response_)); |
| EXPECT_EQ(0, cache_->MemoryBackedSize()); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, OpsFailOnClosedBackendNeverCreated) { |
| cache_->set_delay_backend_creation( |
| true); // Will hang the test if a backend is created. |
| EXPECT_TRUE(Close()); |
| VerifyAllOpsFail(); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, OpsFailOnClosedBackend) { |
| // Create the backend and put something in it. |
| EXPECT_TRUE(Put(body_request_, body_response_)); |
| EXPECT_TRUE(Close()); |
| VerifyAllOpsFail(); |
| } |
| |
| TEST_P(CacheStorageCacheTestP, VerifySerialScheduling) { |
| // Start two operations, the first one is delayed but the second isn't. The |
| // second should wait for the first. |
| EXPECT_TRUE(Keys()); // Opens the backend. |
| DelayableBackend* delayable_backend = cache_->UseDelayableBackend(); |
| delayable_backend->set_delay_open(true); |
| |
| int sequence_out = -1; |
| |
| CacheStorageBatchOperation operation1; |
| operation1.operation_type = CACHE_STORAGE_CACHE_OPERATION_TYPE_PUT; |
| operation1.request = body_request_; |
| operation1.response = body_response_; |
| |
| scoped_ptr<base::RunLoop> close_loop1(new base::RunLoop()); |
| cache_->BatchOperation( |
| std::vector<CacheStorageBatchOperation>(1, operation1), |
| base::Bind(&CacheStorageCacheTest::SequenceCallback, |
| base::Unretained(this), 1, &sequence_out, close_loop1.get())); |
| |
| // Blocks on opening the cache entry. |
| base::RunLoop().RunUntilIdle(); |
| |
| CacheStorageBatchOperation operation2; |
| operation2.operation_type = CACHE_STORAGE_CACHE_OPERATION_TYPE_PUT; |
| operation2.request = body_request_; |
| operation2.response = body_response_; |
| |
| delayable_backend->set_delay_open(false); |
| scoped_ptr<base::RunLoop> close_loop2(new base::RunLoop()); |
| cache_->BatchOperation( |
| std::vector<CacheStorageBatchOperation>(1, operation2), |
| base::Bind(&CacheStorageCacheTest::SequenceCallback, |
| base::Unretained(this), 2, &sequence_out, close_loop2.get())); |
| |
| // The second put operation should wait for the first to complete. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(callback_response_); |
| |
| delayable_backend->OpenEntryContinue(); |
| close_loop1->Run(); |
| EXPECT_EQ(1, sequence_out); |
| close_loop2->Run(); |
| EXPECT_EQ(2, sequence_out); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(CacheStorageCacheTest, |
| CacheStorageCacheTestP, |
| ::testing::Values(false, true)); |
| |
| } // namespace content |