blob: a50ce5ca7fcd33c1b131494884441f763e89de54 [file] [log] [blame]
// 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>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/pickle.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/appcache/appcache_response.h"
#include "content/browser/appcache/appcache_service_impl.h"
#include "content/browser/appcache/mock_appcache_storage.h"
#include "net/base/completion_once_callback.h"
#include "net/base/io_buffer.h"
#include "net/http/http_response_headers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
const int64_t kMockGroupId = 1;
const int64_t kMockCacheId = 1;
const int64_t kMockResponseId = 1;
const int64_t kMissingCacheId = 5;
const int64_t kMissingResponseId = 5;
const char kMockHeaders[] =
"HTTP/1.0 200 OK\0Content-Length: 5\0\0";
const char kMockBody[] = "Hello";
const int kMockBodySize = 5;
class MockResponseReader : public AppCacheResponseReader {
public:
MockResponseReader(int64_t response_id,
net::HttpResponseInfo* info,
int info_size,
const char* data,
int data_size)
: AppCacheResponseReader(response_id, /*disk_cache=*/nullptr),
info_(info),
info_size_(info_size),
data_(data),
data_size_(data_size) {}
void ReadInfo(HttpResponseInfoIOBuffer* info_buf,
OnceCompletionCallback callback) override {
info_buffer_ = info_buf;
callback_ = std::move(callback); // Cleared on completion.
int rv = info_.get() ? info_size_ : net::ERR_FAILED;
info_buffer_->http_info.reset(info_.release());
info_buffer_->response_data_size = data_size_;
ScheduleUserCallback(rv);
}
void ReadData(net::IOBuffer* buf,
int buf_len,
OnceCompletionCallback callback) override {
buffer_ = buf;
buffer_len_ = buf_len;
callback_ = std::move(callback); // Cleared on completion.
if (!data_) {
ScheduleUserCallback(net::ERR_CACHE_READ_FAILURE);
return;
}
DCHECK(buf_len >= data_size_);
memcpy(buf->data(), data_, data_size_);
ScheduleUserCallback(data_size_);
data_size_ = 0;
}
private:
void ScheduleUserCallback(int result) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&MockResponseReader::InvokeUserCompletionCallback,
weak_factory_.GetWeakPtr(), result));
}
std::unique_ptr<net::HttpResponseInfo> info_;
int info_size_;
const char* data_;
int data_size_;
};
} // namespace
class AppCacheServiceImplTest : public testing::Test {
public:
AppCacheServiceImplTest()
: kOriginURL("http://hello/"),
kOrigin(url::Origin::Create(kOriginURL)),
kManifestUrl(kOriginURL.Resolve("manifest")),
service_(new AppCacheServiceImpl(nullptr)),
delete_result_(net::OK),
delete_completion_count_(0) {
// Setup to use mock storage.
service_->storage_.reset(new MockAppCacheStorage(service_.get()));
}
void OnDeleteAppCachesComplete(int result) {
delete_result_ = result;
++delete_completion_count_;
}
MockAppCacheStorage* mock_storage() {
return static_cast<MockAppCacheStorage*>(service_->storage());
}
void ResetStorage() {
service_->storage_.reset(new MockAppCacheStorage(service_.get()));
}
bool IsGroupStored(const GURL& manifest_url) {
return mock_storage()->IsGroupForManifestStored(manifest_url);
}
int CountPendingHelpers() {
return service_->pending_helpers_.size();
}
void SetupMockGroup() {
std::unique_ptr<net::HttpResponseInfo> info(MakeMockResponseInfo());
const int kMockInfoSize = GetResponseInfoSize(info.get());
// Create a mock group, cache, and entry and stuff them into mock storage.
scoped_refptr<AppCacheGroup> group(
new AppCacheGroup(service_->storage(), kManifestUrl, kMockGroupId));
scoped_refptr<AppCache> cache(
new AppCache(service_->storage(), kMockCacheId));
cache->AddEntry(
kManifestUrl,
AppCacheEntry(AppCacheEntry::MANIFEST, kMockResponseId,
kMockInfoSize + kMockBodySize));
cache->set_complete(true);
group->AddCache(cache.get());
mock_storage()->AddStoredGroup(group.get());
mock_storage()->AddStoredCache(cache.get());
}
void SetupMockReader(
bool valid_info, bool valid_data, bool valid_size) {
net::HttpResponseInfo* info = valid_info ? MakeMockResponseInfo() : nullptr;
int info_size = info ? GetResponseInfoSize(info) : 0;
const char* data = valid_data ? kMockBody : nullptr;
int data_size = valid_size ? kMockBodySize : 3;
mock_storage()->SimulateResponseReader(
new MockResponseReader(kMockResponseId, info, info_size,
data, data_size));
}
net::HttpResponseInfo* MakeMockResponseInfo() {
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(
std::string(kMockHeaders, arraysize(kMockHeaders)));
return info;
}
int GetResponseInfoSize(const net::HttpResponseInfo* info) {
base::Pickle pickle;
return PickleResponseInfo(&pickle, info);
}
int PickleResponseInfo(base::Pickle* pickle,
const net::HttpResponseInfo* info) {
const bool kSkipTransientHeaders = true;
const bool kTruncated = false;
info->Persist(pickle, kSkipTransientHeaders, kTruncated);
return pickle->size();
}
net::CompletionOnceCallback deletion_callback() {
return base::BindOnce(&AppCacheServiceImplTest::OnDeleteAppCachesComplete,
base::Unretained(this));
}
const GURL kOriginURL;
const url::Origin kOrigin;
const GURL kManifestUrl;
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<AppCacheServiceImpl> service_;
int delete_result_;
int delete_completion_count_;
};
TEST_F(AppCacheServiceImplTest, DeleteAppCachesForOrigin) {
// Without giving mock storage simiulated info, should fail.
service_->DeleteAppCachesForOrigin(kOrigin, deletion_callback());
EXPECT_EQ(0, delete_completion_count_);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, delete_completion_count_);
EXPECT_EQ(net::ERR_FAILED, delete_result_);
delete_completion_count_ = 0;
// Should succeed given an empty info collection.
mock_storage()->SimulateGetAllInfo(new content::AppCacheInfoCollection);
service_->DeleteAppCachesForOrigin(kOrigin, deletion_callback());
EXPECT_EQ(0, delete_completion_count_);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, delete_completion_count_);
EXPECT_EQ(net::OK, delete_result_);
delete_completion_count_ = 0;
scoped_refptr<AppCacheInfoCollection> info(new AppCacheInfoCollection);
// Should succeed given a non-empty info collection.
AppCacheInfo mock_manifest_1;
AppCacheInfo mock_manifest_2;
AppCacheInfo mock_manifest_3;
mock_manifest_1.manifest_url = kOriginURL.Resolve("manifest1");
mock_manifest_2.manifest_url = kOriginURL.Resolve("manifest2");
mock_manifest_3.manifest_url = kOriginURL.Resolve("manifest3");
AppCacheInfoVector info_vector;
info_vector.push_back(mock_manifest_1);
info_vector.push_back(mock_manifest_2);
info_vector.push_back(mock_manifest_3);
info->infos_by_origin[kOrigin] = info_vector;
mock_storage()->SimulateGetAllInfo(info.get());
service_->DeleteAppCachesForOrigin(kOrigin, deletion_callback());
EXPECT_EQ(0, delete_completion_count_);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, delete_completion_count_);
EXPECT_EQ(net::OK, delete_result_);
delete_completion_count_ = 0;
// Should fail if storage fails to delete.
info->infos_by_origin[kOrigin] = info_vector;
mock_storage()->SimulateGetAllInfo(info.get());
mock_storage()->SimulateMakeGroupObsoleteFailure();
service_->DeleteAppCachesForOrigin(kOrigin, deletion_callback());
EXPECT_EQ(0, delete_completion_count_);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, delete_completion_count_);
EXPECT_EQ(net::ERR_FAILED, delete_result_);
delete_completion_count_ = 0;
// Should complete with abort error if the service is deleted
// prior to a delete completion.
service_->DeleteAppCachesForOrigin(kOrigin, deletion_callback());
EXPECT_EQ(0, delete_completion_count_);
service_.reset(); // kill it
EXPECT_EQ(1, delete_completion_count_);
EXPECT_EQ(net::ERR_ABORTED, delete_result_);
delete_completion_count_ = 0;
// Let any tasks lingering from the sudden deletion run and verify
// no other completion calls occur.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, delete_completion_count_);
}
TEST_F(AppCacheServiceImplTest, CheckAppCacheResponse) {
// Check a non-existing manifest.
EXPECT_FALSE(IsGroupStored(kManifestUrl));
service_->CheckAppCacheResponse(kManifestUrl, 1, 1);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, CountPendingHelpers());
EXPECT_FALSE(IsGroupStored(kManifestUrl));
ResetStorage();
// Check a response that looks good.
// Nothing should be deleted.
SetupMockGroup();
EXPECT_TRUE(IsGroupStored(kManifestUrl));
SetupMockReader(true, true, true);
service_->CheckAppCacheResponse(kManifestUrl, kMockCacheId, kMockResponseId);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, CountPendingHelpers());
EXPECT_TRUE(IsGroupStored(kManifestUrl));
ResetStorage();
// Check a response for which there is no cache entry.
// The group should get deleted.
SetupMockGroup();
service_->CheckAppCacheResponse(kManifestUrl, kMockCacheId,
kMissingResponseId);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, CountPendingHelpers());
EXPECT_FALSE(IsGroupStored(kManifestUrl));
ResetStorage();
// Check a response for which there is no manifest entry in a newer version
// of the cache. Nothing should get deleted in this case.
SetupMockGroup();
service_->CheckAppCacheResponse(kManifestUrl, kMissingCacheId,
kMissingResponseId);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, CountPendingHelpers());
EXPECT_TRUE(IsGroupStored(kManifestUrl));
ResetStorage();
// Check a response with bad headers.
SetupMockGroup();
service_->CheckAppCacheResponse(kManifestUrl, kMockCacheId, kMockResponseId);
SetupMockReader(false, true, true);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, CountPendingHelpers());
EXPECT_FALSE(IsGroupStored(kManifestUrl));
ResetStorage();
// Check a response with bad data.
SetupMockGroup();
service_->CheckAppCacheResponse(kManifestUrl, kMockCacheId, kMockResponseId);
SetupMockReader(true, false, true);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, CountPendingHelpers());
EXPECT_FALSE(IsGroupStored(kManifestUrl));
ResetStorage();
// Check a response with truncated data.
SetupMockGroup();
service_->CheckAppCacheResponse(kManifestUrl, kMockCacheId, kMockResponseId);
SetupMockReader(true, true, false);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, CountPendingHelpers());
EXPECT_FALSE(IsGroupStored(kManifestUrl));
ResetStorage();
service_.reset(); // Clean up.
base::RunLoop().RunUntilIdle();
}
// Just tests the backoff scheduling function, not the actual reinit function.
TEST_F(AppCacheServiceImplTest, ScheduleReinitialize) {
const base::TimeDelta kNoDelay;
const base::TimeDelta kOneSecond(base::TimeDelta::FromSeconds(1));
const base::TimeDelta k30Seconds(base::TimeDelta::FromSeconds(30));
const base::TimeDelta kOneHour(base::TimeDelta::FromHours(1));
// Do things get initialized as expected?
std::unique_ptr<AppCacheServiceImpl> service(
new AppCacheServiceImpl(nullptr));
EXPECT_TRUE(service->last_reinit_time_.is_null());
EXPECT_FALSE(service->reinit_timer_.IsRunning());
EXPECT_EQ(kNoDelay, service->next_reinit_delay_);
// Do we see artifacts of the timer pending and such?
service->ScheduleReinitialize();
EXPECT_TRUE(service->reinit_timer_.IsRunning());
EXPECT_EQ(kNoDelay, service->reinit_timer_.GetCurrentDelay());
EXPECT_EQ(k30Seconds, service->next_reinit_delay_);
// Nothing should change if already scheduled
service->ScheduleReinitialize();
EXPECT_TRUE(service->reinit_timer_.IsRunning());
EXPECT_EQ(kNoDelay, service->reinit_timer_.GetCurrentDelay());
EXPECT_EQ(k30Seconds, service->next_reinit_delay_);
// Does the delay increase as expected?
service->reinit_timer_.Stop();
service->last_reinit_time_ = base::Time::Now() - kOneSecond;
service->ScheduleReinitialize();
EXPECT_TRUE(service->reinit_timer_.IsRunning());
EXPECT_EQ(k30Seconds, service->reinit_timer_.GetCurrentDelay());
EXPECT_EQ(k30Seconds + k30Seconds, service->next_reinit_delay_);
// Does the delay reset as expected?
service->reinit_timer_.Stop();
service->last_reinit_time_ = base::Time::Now() -
base::TimeDelta::FromHours(2);
service->ScheduleReinitialize();
EXPECT_TRUE(service->reinit_timer_.IsRunning());
EXPECT_EQ(kNoDelay, service->reinit_timer_.GetCurrentDelay());
EXPECT_EQ(k30Seconds, service->next_reinit_delay_);
// Does the delay max out as expected?
service->reinit_timer_.Stop();
service->last_reinit_time_ = base::Time::Now() - kOneSecond;
service->next_reinit_delay_ = kOneHour;
service->ScheduleReinitialize();
EXPECT_TRUE(service->reinit_timer_.IsRunning());
EXPECT_EQ(kOneHour, service->reinit_timer_.GetCurrentDelay());
EXPECT_EQ(kOneHour, service->next_reinit_delay_);
// Fine to delete while pending.
service.reset(nullptr);
}
} // namespace content