blob: 5fccade152141a10d57e25e65a5fcc172ae75ad1 [file] [log] [blame]
// 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 <stack>
#include "base/message_loop.h"
#include "base/thread.h"
#include "base/waitable_event.h"
#include "net/base/net_errors.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "webkit/appcache/appcache.h"
#include "webkit/appcache/appcache_database.h"
#include "webkit/appcache/appcache_entry.h"
#include "webkit/appcache/appcache_group.h"
#include "webkit/appcache/appcache_policy.h"
#include "webkit/appcache/appcache_service.h"
#include "webkit/appcache/appcache_storage_impl.h"
#include "webkit/tools/test_shell/simple_appcache_system.h"
namespace appcache {
namespace {
const base::Time kZeroTime;
const GURL kManifestUrl("http://blah/manifest");
const GURL kManifestUrl2("http://blah/manifest2");
const GURL kEntryUrl("http://blah/entry");
const GURL kEntryUrl2("http://blah/entry2");
const GURL kFallbackNamespace("http://blah/fallback_namespace/");
const GURL kFallbackNamespace2("http://blah/fallback_namespace/longer");
const GURL kFallbackTestUrl("http://blah/fallback_namespace/longer/test");
const GURL kOnlineNamespace("http://blah/online_namespace");
// For the duration of this test case, we hijack the AppCacheThread API
// calls and implement them in terms of the io and db threads created here.
scoped_ptr<base::Thread> io_thread;
scoped_ptr<base::Thread> db_thread;
class TestThreadProvider : public SimpleAppCacheSystem::ThreadProvider {
public:
virtual bool PostTask(
int id,
const tracked_objects::Location& from_here,
Task* task) {
GetMessageLoop(id)->PostTask(from_here, task);
return true;
}
virtual bool CurrentlyOn(int id) {
return MessageLoop::current() == GetMessageLoop(id);
}
MessageLoop* GetMessageLoop(int id) {
DCHECK(io_thread.get() && db_thread.get());
if (id == SimpleAppCacheSystem::IO_THREAD_ID)
return io_thread->message_loop();
if (id == SimpleAppCacheSystem::DB_THREAD_ID)
return db_thread->message_loop();
NOTREACHED() << "Invalid AppCacheThreadID value";
return NULL;
}
};
TestThreadProvider thread_provider;
} // namespace
class AppCacheStorageImplTest : public testing::Test {
public:
class MockStorageDelegate : public AppCacheStorage::Delegate {
public:
explicit MockStorageDelegate(AppCacheStorageImplTest* test)
: loaded_cache_id_(0), stored_group_success_(false),
would_exceed_quota_(false), obsoleted_success_(false),
found_cache_id_(kNoCacheId), found_blocked_by_policy_(false),
test_(test) {
}
void OnCacheLoaded(AppCache* cache, int64 cache_id) {
loaded_cache_ = cache;
loaded_cache_id_ = cache_id;
test_->ScheduleNextTask();
}
void OnGroupLoaded(AppCacheGroup* group, const GURL& manifest_url) {
loaded_group_ = group;
loaded_manifest_url_ = manifest_url;
loaded_groups_newest_cache_ = group ? group->newest_complete_cache()
: NULL;
test_->ScheduleNextTask();
}
void OnGroupAndNewestCacheStored(
AppCacheGroup* group, AppCache* newest_cache, bool success,
bool would_exceed_quota) {
stored_group_ = group;
stored_group_success_ = success;
would_exceed_quota_ = would_exceed_quota;
test_->ScheduleNextTask();
}
void OnGroupMadeObsolete(AppCacheGroup* group, bool success) {
obsoleted_group_ = group;
obsoleted_success_ = success;
test_->ScheduleNextTask();
}
void OnMainResponseFound(const GURL& url, const AppCacheEntry& entry,
const AppCacheEntry& fallback_entry,
int64 cache_id, const GURL& manifest_url,
bool was_blocked_by_policy) {
found_url_ = url;
found_entry_ = entry;
found_fallback_entry_ = fallback_entry;
found_cache_id_ = cache_id;
found_manifest_url_ = manifest_url;
found_blocked_by_policy_ = was_blocked_by_policy;
test_->ScheduleNextTask();
}
scoped_refptr<AppCache> loaded_cache_;
int64 loaded_cache_id_;
scoped_refptr<AppCacheGroup> loaded_group_;
GURL loaded_manifest_url_;
scoped_refptr<AppCache> loaded_groups_newest_cache_;
scoped_refptr<AppCacheGroup> stored_group_;
bool stored_group_success_;
bool would_exceed_quota_;
scoped_refptr<AppCacheGroup> obsoleted_group_;
bool obsoleted_success_;
GURL found_url_;
AppCacheEntry found_entry_;
AppCacheEntry found_fallback_entry_;
int64 found_cache_id_;
GURL found_manifest_url_;
bool found_blocked_by_policy_;
AppCacheStorageImplTest* test_;
};
class MockAppCachePolicy : public AppCachePolicy {
public:
explicit MockAppCachePolicy(AppCacheStorageImplTest* test)
: can_load_return_value_(true), can_create_return_value_(0),
callback_(NULL), test_(test) {
}
virtual bool CanLoadAppCache(const GURL& manifest_url) {
requested_manifest_url_ = manifest_url;
return can_load_return_value_;
}
virtual int CanCreateAppCache(const GURL& manifest_url,
net::CompletionCallback* callback) {
requested_manifest_url_ = manifest_url;
callback_ = callback;
if (can_create_return_value_ == net::ERR_IO_PENDING)
test_->ScheduleNextTask();
return can_create_return_value_;
}
bool can_load_return_value_;
int can_create_return_value_;
GURL requested_manifest_url_;
net::CompletionCallback* callback_;
AppCacheStorageImplTest* test_;
};
// Helper class run a test on our io_thread. The io_thread
// is spun up once and reused for all tests.
template <class Method>
class WrapperTask : public Task {
public:
WrapperTask(AppCacheStorageImplTest* test, Method method)
: test_(test), method_(method) {
}
virtual void Run() {
test_->SetUpTest();
// Ensure InitTask execution prior to conducting a test.
test_->FlushDbThreadTasks();
// We also have to wait for InitTask completion call to be performed
// on the IO thread prior to running the test. Its guaranteed to be
// queued by this time.
MessageLoop::current()->PostTask(FROM_HERE,
NewRunnableFunction(&RunMethod, test_, method_));
}
static void RunMethod(AppCacheStorageImplTest* test, Method method) {
(test->*method)();
}
private:
AppCacheStorageImplTest* test_;
Method method_;
};
static void SetUpTestCase() {
io_thread.reset(new base::Thread("AppCacheTest.IOThread"));
base::Thread::Options options(MessageLoop::TYPE_IO, 0);
ASSERT_TRUE(io_thread->StartWithOptions(options));
db_thread.reset(new base::Thread("AppCacheTest::DBThread"));
ASSERT_TRUE(db_thread->Start());
SimpleAppCacheSystem::set_thread_provider(&thread_provider);
}
static void TearDownTestCase() {
SimpleAppCacheSystem::set_thread_provider(NULL);
io_thread.reset(NULL);
db_thread.reset(NULL);
}
// Test harness --------------------------------------------------
AppCacheStorageImplTest()
: ALLOW_THIS_IN_INITIALIZER_LIST(policy_(this)) {
}
template <class Method>
void RunTestOnIOThread(Method method) {
test_finished_event_ .reset(new base::WaitableEvent(false, false));
io_thread->message_loop()->PostTask(
FROM_HERE, new WrapperTask<Method>(this, method));
test_finished_event_->Wait();
}
void SetUpTest() {
DCHECK(MessageLoop::current() == io_thread->message_loop());
service_.reset(new AppCacheService);
service_->Initialize(FilePath(), NULL);
delegate_.reset(new MockStorageDelegate(this));
}
void TearDownTest() {
DCHECK(MessageLoop::current() == io_thread->message_loop());
storage()->CancelDelegateCallbacks(delegate());
group_ = NULL;
cache_ = NULL;
cache2_ = NULL;
delegate_.reset();
service_.reset();
FlushDbThreadTasks();
}
void TestFinished() {
// We unwind the stack prior to finishing up to let stack
// based objects get deleted.
DCHECK(MessageLoop::current() == io_thread->message_loop());
MessageLoop::current()->PostTask(FROM_HERE,
NewRunnableMethod(this, &AppCacheStorageImplTest::TestFinishedUnwound));
}
void TestFinishedUnwound() {
TearDownTest();
test_finished_event_->Signal();
}
void PushNextTask(Task* task) {
task_stack_.push(task);
}
void ScheduleNextTask() {
DCHECK(MessageLoop::current() == io_thread->message_loop());
if (task_stack_.empty()) {
return;
}
MessageLoop::current()->PostTask(FROM_HERE, task_stack_.top());
task_stack_.pop();
}
static void SignalEvent(base::WaitableEvent* event) {
event->Signal();
}
void FlushDbThreadTasks() {
// We pump a task thru the db thread to ensure any tasks previously
// scheduled on that thread have been performed prior to return.
base::WaitableEvent event(false, false);
db_thread->message_loop()->PostTask(FROM_HERE,
NewRunnableFunction(&AppCacheStorageImplTest::SignalEvent,
&event));
event.Wait();
}
// LoadCache_Miss ----------------------------------------------------
void LoadCache_Miss() {
// Attempt to load a cache that doesn't exist. Should
// complete asyncly.
PushNextTask(NewRunnableMethod(
this, &AppCacheStorageImplTest::Verify_LoadCache_Miss));
storage()->LoadCache(111, delegate());
EXPECT_NE(111, delegate()->loaded_cache_id_);
}
void Verify_LoadCache_Miss() {
EXPECT_EQ(111, delegate()->loaded_cache_id_);
EXPECT_FALSE(delegate()->loaded_cache_);
TestFinished();
}
// LoadCache_NearHit -------------------------------------------------
void LoadCache_NearHit() {
// Attempt to load a cache that is currently in use
// and does not require loading from storage. This
// load should complete syncly.
// Setup some preconditions. Make an 'unstored' cache for
// us to load. The ctor should put it in the working set.
int64 cache_id = storage()->NewCacheId();
scoped_refptr<AppCache> cache = new AppCache(service(), cache_id);
// Conduct the test.
storage()->LoadCache(cache_id, delegate());
EXPECT_EQ(cache_id, delegate()->loaded_cache_id_);
EXPECT_EQ(cache.get(), delegate()->loaded_cache_.get());
TestFinished();
}
// CreateGroup --------------------------------------------
void CreateGroupInEmptyOrigin() {
// Attempt to load a group that doesn't exist, one should
// be created for us, but not stored.
// Since the origin has no groups, the storage class will respond
// syncly.
storage()->LoadOrCreateGroup(kManifestUrl, delegate());
Verify_CreateGroup();
}
void CreateGroupInPopulatedOrigin() {
// Attempt to load a group that doesn't exist, one should
// be created for us, but not stored.
PushNextTask(NewRunnableMethod(
this, &AppCacheStorageImplTest::Verify_CreateGroup));
// Since the origin has groups, storage class will have to
// consult the database and completion will be async.
storage()->origins_with_groups_.insert(kManifestUrl.GetOrigin());
storage()->LoadOrCreateGroup(kManifestUrl, delegate());
EXPECT_FALSE(delegate()->loaded_group_.get());
}
void Verify_CreateGroup() {
EXPECT_EQ(kManifestUrl, delegate()->loaded_manifest_url_);
EXPECT_TRUE(delegate()->loaded_group_.get());
EXPECT_TRUE(delegate()->loaded_group_->HasOneRef());
EXPECT_FALSE(delegate()->loaded_group_->newest_complete_cache());
// Should not have been stored in the database.
AppCacheDatabase::GroupRecord record;
EXPECT_FALSE(database()->FindGroup(
delegate()->loaded_group_->group_id(), &record));
TestFinished();
}
// LoadGroupAndCache_FarHit --------------------------------------
void LoadGroupAndCache_FarHit() {
// Attempt to load a cache that is not currently in use
// and does require loading from disk. This
// load should complete asyncly.
PushNextTask(NewRunnableMethod(
this, &AppCacheStorageImplTest::Verify_LoadCache_Far_Hit));
// Setup some preconditions. Create a group and newest cache that
// appear to be "stored" and "not currently in use".
MakeCacheAndGroup(kManifestUrl, 1, 1, true);
group_ = NULL;
cache_ = NULL;
// Conduct the cache load test, completes async
storage()->LoadCache(1, delegate());
}
void Verify_LoadCache_Far_Hit() {
EXPECT_TRUE(delegate()->loaded_cache_);
EXPECT_TRUE(delegate()->loaded_cache_->HasOneRef());
EXPECT_EQ(1, delegate()->loaded_cache_id_);
// The group should also have been loaded.
EXPECT_TRUE(delegate()->loaded_cache_->owning_group());
EXPECT_TRUE(delegate()->loaded_cache_->owning_group()->HasOneRef());
EXPECT_EQ(1, delegate()->loaded_cache_->owning_group()->group_id());
// Drop things from the working set.
delegate()->loaded_cache_ = NULL;
EXPECT_FALSE(delegate()->loaded_group_);
// Conduct the group load test, also complete asyncly.
PushNextTask(NewRunnableMethod(
this, &AppCacheStorageImplTest::Verify_LoadGroup_Far_Hit));
storage()->LoadOrCreateGroup(kManifestUrl, delegate());
}
void Verify_LoadGroup_Far_Hit() {
EXPECT_TRUE(delegate()->loaded_group_);
EXPECT_EQ(kManifestUrl, delegate()->loaded_manifest_url_);
EXPECT_TRUE(delegate()->loaded_group_->newest_complete_cache());
delegate()->loaded_groups_newest_cache_ = NULL;
EXPECT_TRUE(delegate()->loaded_group_->HasOneRef());
TestFinished();
}
// StoreNewGroup --------------------------------------
void StoreNewGroup() {
// Store a group and its newest cache. Should complete asyncly.
PushNextTask(NewRunnableMethod(
this, &AppCacheStorageImplTest::Verify_StoreNewGroup));
// Setup some preconditions. Create a group and newest cache that
// appear to be "unstored".
group_ = new AppCacheGroup(
service(), kManifestUrl, storage()->NewGroupId());
cache_ = new AppCache(service(), storage()->NewCacheId());
// Hold a ref to the cache simulate the UpdateJob holding that ref,
// and hold a ref to the group to simulate the CacheHost holding that ref.
// Conduct the store test.
storage()->StoreGroupAndNewestCache(group_, cache_, delegate());
EXPECT_FALSE(delegate()->stored_group_success_);
}
void Verify_StoreNewGroup() {
EXPECT_TRUE(delegate()->stored_group_success_);
EXPECT_EQ(group_.get(), delegate()->stored_group_.get());
EXPECT_EQ(cache_.get(), group_->newest_complete_cache());
EXPECT_TRUE(cache_->is_complete());
// Should have been stored in the database.
AppCacheDatabase::GroupRecord group_record;
AppCacheDatabase::CacheRecord cache_record;
EXPECT_TRUE(database()->FindGroup(group_->group_id(), &group_record));
EXPECT_TRUE(database()->FindCache(cache_->cache_id(), &cache_record));
TestFinished();
}
// StoreExistingGroup --------------------------------------
void StoreExistingGroup() {
// Store a group and its newest cache. Should complete asyncly.
PushNextTask(NewRunnableMethod(
this, &AppCacheStorageImplTest::Verify_StoreExistingGroup));
// Setup some preconditions. Create a group and old complete cache
// that appear to be "stored"
MakeCacheAndGroup(kManifestUrl, 1, 1, true);
// And a newest unstored complete cache.
cache2_ = new AppCache(service(), 2);
// Conduct the test.
storage()->StoreGroupAndNewestCache(group_, cache2_, delegate());
EXPECT_FALSE(delegate()->stored_group_success_);
}
void Verify_StoreExistingGroup() {
EXPECT_TRUE(delegate()->stored_group_success_);
EXPECT_EQ(group_.get(), delegate()->stored_group_.get());
EXPECT_EQ(cache2_.get(), group_->newest_complete_cache());
EXPECT_TRUE(cache2_->is_complete());
// The new cache should have been stored in the database.
AppCacheDatabase::GroupRecord group_record;
AppCacheDatabase::CacheRecord cache_record;
EXPECT_TRUE(database()->FindGroup(1, &group_record));
EXPECT_TRUE(database()->FindCache(2, &cache_record));
// The old cache should have been deleted
EXPECT_FALSE(database()->FindCache(1, &cache_record));
TestFinished();
}
// StoreExistingGroupExistingCache -------------------------------
void StoreExistingGroupExistingCache() {
// Store a group with updates to its existing newest complete cache.
// Setup some preconditions. Create a group and a complete cache that
// appear to be "stored".
// Setup some preconditions. Create a group and old complete cache
// that appear to be "stored"
MakeCacheAndGroup(kManifestUrl, 1, 1, true);
// Change the cache.
base::Time now = base::Time::Now();
cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::MASTER, 1, 100));
cache_->set_update_time(now);
PushNextTask(NewRunnableMethod(
this, &AppCacheStorageImplTest::Verify_StoreExistingGroupExistingCache,
now));
// Conduct the test.
EXPECT_EQ(cache_, group_->newest_complete_cache());
storage()->StoreGroupAndNewestCache(group_, cache_, delegate());
EXPECT_FALSE(delegate()->stored_group_success_);
}
void Verify_StoreExistingGroupExistingCache(
base::Time expected_update_time) {
EXPECT_TRUE(delegate()->stored_group_success_);
EXPECT_EQ(cache_, group_->newest_complete_cache());
AppCacheDatabase::CacheRecord cache_record;
EXPECT_TRUE(database()->FindCache(1, &cache_record));
EXPECT_EQ(1, cache_record.cache_id);
EXPECT_EQ(1, cache_record.group_id);
EXPECT_FALSE(cache_record.online_wildcard);
EXPECT_TRUE(expected_update_time == cache_record.update_time);
EXPECT_EQ(100, cache_record.cache_size);
std::vector<AppCacheDatabase::EntryRecord> entry_records;
EXPECT_TRUE(database()->FindEntriesForCache(1, &entry_records));
EXPECT_EQ(1U, entry_records.size());
EXPECT_EQ(1 , entry_records[0].cache_id);
EXPECT_EQ(kEntryUrl, entry_records[0].url);
EXPECT_EQ(AppCacheEntry::MASTER, entry_records[0].flags);
EXPECT_EQ(1, entry_records[0].response_id);
EXPECT_EQ(100, entry_records[0].response_size);
TestFinished();
}
// FailStoreGroup --------------------------------------
void FailStoreGroup() {
// Store a group and its newest cache. Should complete asyncly.
PushNextTask(NewRunnableMethod(
this, &AppCacheStorageImplTest::Verify_FailStoreGroup));
// Set a low quota to force a failure.
const GURL kOrigin(kManifestUrl.GetOrigin());
EXPECT_EQ(-1L, storage()->GetOriginQuotaInMemory(kOrigin));
storage()->SetOriginQuotaInMemory(kManifestUrl.GetOrigin(), 0);
EXPECT_EQ(0L, storage()->GetOriginQuotaInMemory(kOrigin));
// Setup some preconditions. Create a group and newest cache that
// appear to be "unstored".
group_ = new AppCacheGroup(
service(), kManifestUrl, storage()->NewGroupId());
cache_ = new AppCache(service(), storage()->NewCacheId());
cache_->AddEntry(kManifestUrl,
AppCacheEntry(AppCacheEntry::MANIFEST, 1, 1024));
// Hold a ref to the cache simulate the UpdateJob holding that ref,
// and hold a ref to the group to simulate the CacheHost holding that ref.
// Conduct the store test.
storage()->StoreGroupAndNewestCache(group_, cache_, delegate());
EXPECT_FALSE(delegate()->stored_group_success_); // Expected to be async.
}
void Verify_FailStoreGroup() {
EXPECT_FALSE(delegate()->stored_group_success_);
EXPECT_TRUE(delegate()->would_exceed_quota_);
// Should not have been stored in the database.
AppCacheDatabase::GroupRecord group_record;
AppCacheDatabase::CacheRecord cache_record;
EXPECT_FALSE(database()->FindGroup(group_->group_id(), &group_record));
EXPECT_FALSE(database()->FindCache(cache_->cache_id(), &cache_record));
const GURL kOrigin(kManifestUrl.GetOrigin());
storage()->ResetOriginQuotaInMemory(kOrigin);
EXPECT_EQ(-1L, storage()->GetOriginQuotaInMemory(kOrigin));
TestFinished();
}
// MakeGroupObsolete -------------------------------
void MakeGroupObsolete() {
// Make a group obsolete, should complete asyncly.
PushNextTask(NewRunnableMethod(
this, &AppCacheStorageImplTest::Verify_MakeGroupObsolete));
// Setup some preconditions. Create a group and newest cache that
// appears to be "stored" and "currently in use".
MakeCacheAndGroup(kManifestUrl, 1, 1, true);
EXPECT_FALSE(storage()->origins_with_groups_.empty());
// Also insert some related records.
AppCacheDatabase::EntryRecord entry_record;
entry_record.cache_id = 1;
entry_record.flags = AppCacheEntry::FALLBACK;
entry_record.response_id = 1;
entry_record.url = kEntryUrl;
EXPECT_TRUE(database()->InsertEntry(&entry_record));
AppCacheDatabase::FallbackNameSpaceRecord fallback_namespace_record;
fallback_namespace_record.cache_id = 1;
fallback_namespace_record.fallback_entry_url = kEntryUrl;
fallback_namespace_record.namespace_url = kFallbackNamespace;
fallback_namespace_record.origin = kManifestUrl.GetOrigin();
EXPECT_TRUE(
database()->InsertFallbackNameSpace(&fallback_namespace_record));
AppCacheDatabase::OnlineWhiteListRecord online_whitelist_record;
online_whitelist_record.cache_id = 1;
online_whitelist_record.namespace_url = kOnlineNamespace;
EXPECT_TRUE(database()->InsertOnlineWhiteList(&online_whitelist_record));
// Conduct the test.
storage()->MakeGroupObsolete(group_, delegate());
EXPECT_FALSE(group_->is_obsolete());
}
void Verify_MakeGroupObsolete() {
EXPECT_TRUE(delegate()->obsoleted_success_);
EXPECT_EQ(group_.get(), delegate()->obsoleted_group_.get());
EXPECT_TRUE(group_->is_obsolete());
EXPECT_TRUE(storage()->origins_with_groups_.empty());
// The cache and group have been deleted from the database.
AppCacheDatabase::GroupRecord group_record;
AppCacheDatabase::CacheRecord cache_record;
EXPECT_FALSE(database()->FindGroup(1, &group_record));
EXPECT_FALSE(database()->FindCache(1, &cache_record));
// The related records should have been deleted too.
std::vector<AppCacheDatabase::EntryRecord> entry_records;
database()->FindEntriesForCache(1, &entry_records);
EXPECT_TRUE(entry_records.empty());
std::vector<AppCacheDatabase::FallbackNameSpaceRecord> fallback_records;
database()->FindFallbackNameSpacesForCache(1, &fallback_records);
EXPECT_TRUE(fallback_records.empty());
std::vector<AppCacheDatabase::OnlineWhiteListRecord> whitelist_records;
database()->FindOnlineWhiteListForCache(1, &whitelist_records);
EXPECT_TRUE(whitelist_records.empty());
TestFinished();
}
// MarkEntryAsForeign -------------------------------
void MarkEntryAsForeign() {
// Setup some preconditions. Create a cache with an entry
// in storage and in the working set.
MakeCacheAndGroup(kManifestUrl, 1, 1, true);
cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::EXPLICIT));
AppCacheDatabase::EntryRecord entry_record;
entry_record.cache_id = 1;
entry_record.url = kEntryUrl;
entry_record.flags = AppCacheEntry::EXPLICIT;
entry_record.response_id = 0;
EXPECT_TRUE(database()->InsertEntry(&entry_record));
EXPECT_FALSE(cache_->GetEntry(kEntryUrl)->IsForeign());
// Conduct the test.
storage()->MarkEntryAsForeign(kEntryUrl, 1);
// The entry in the working set should have been updated syncly.
EXPECT_TRUE(cache_->GetEntry(kEntryUrl)->IsForeign());
EXPECT_TRUE(cache_->GetEntry(kEntryUrl)->IsExplicit());
// And the entry in storage should also be updated, but that
// happens asyncly on the db thread.
FlushDbThreadTasks();
AppCacheDatabase::EntryRecord entry_record2;
EXPECT_TRUE(database()->FindEntry(1, kEntryUrl, &entry_record2));
EXPECT_EQ(AppCacheEntry::EXPLICIT | AppCacheEntry::FOREIGN,
entry_record2.flags);
TestFinished();
}
// MarkEntryAsForeignWithLoadInProgress -------------------------------
void MarkEntryAsForeignWithLoadInProgress() {
PushNextTask(NewRunnableMethod(this,
&AppCacheStorageImplTest::Verify_MarkEntryAsForeignWithLoadInProgress));
// Setup some preconditions. Create a cache with an entry
// in storage, but not in the working set.
MakeCacheAndGroup(kManifestUrl, 1, 1, true);
cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::EXPLICIT));
AppCacheDatabase::EntryRecord entry_record;
entry_record.cache_id = 1;
entry_record.url = kEntryUrl;
entry_record.flags = AppCacheEntry::EXPLICIT;
entry_record.response_id = 0;
EXPECT_TRUE(database()->InsertEntry(&entry_record));
EXPECT_FALSE(cache_->GetEntry(kEntryUrl)->IsForeign());
EXPECT_TRUE(cache_->HasOneRef());
cache_ = NULL;
group_ = NULL;
// Conduct the test, start a cache load, and prior to completion
// of that load, mark the entry as foreign.
storage()->LoadCache(1, delegate());
storage()->MarkEntryAsForeign(kEntryUrl, 1);
}
void Verify_MarkEntryAsForeignWithLoadInProgress() {
EXPECT_EQ(1, delegate()->loaded_cache_id_);
EXPECT_TRUE(delegate()->loaded_cache_.get());
// The entry in the working set should have been updated upon load.
EXPECT_TRUE(delegate()->loaded_cache_->GetEntry(kEntryUrl)->IsForeign());
EXPECT_TRUE(delegate()->loaded_cache_->GetEntry(kEntryUrl)->IsExplicit());
// And the entry in storage should also be updated.
FlushDbThreadTasks();
AppCacheDatabase::EntryRecord entry_record;
EXPECT_TRUE(database()->FindEntry(1, kEntryUrl, &entry_record));
EXPECT_EQ(AppCacheEntry::EXPLICIT | AppCacheEntry::FOREIGN,
entry_record.flags);
TestFinished();
}
// FindNoMainResponse -------------------------------
void FindNoMainResponse() {
PushNextTask(NewRunnableMethod(
this, &AppCacheStorageImplTest::Verify_FindNoMainResponse));
// Conduct the test.
storage()->FindResponseForMainRequest(kEntryUrl, delegate());
EXPECT_NE(kEntryUrl, delegate()->found_url_);
}
void Verify_FindNoMainResponse() {
EXPECT_EQ(kEntryUrl, delegate()->found_url_);
// If the request was blocked by a policy, the manifest url is still valid.
EXPECT_TRUE(delegate()->found_manifest_url_.is_empty() ||
delegate()->found_blocked_by_policy_);
EXPECT_EQ(kNoCacheId, delegate()->found_cache_id_);
EXPECT_EQ(kNoResponseId, delegate()->found_entry_.response_id());
EXPECT_EQ(kNoResponseId, delegate()->found_fallback_entry_.response_id());
EXPECT_EQ(0, delegate()->found_entry_.types());
EXPECT_EQ(0, delegate()->found_fallback_entry_.types());
TestFinished();
}
// BasicFindMainResponse -------------------------------
void BasicFindMainResponseInDatabase() {
BasicFindMainResponse(true, false);
}
void BasicFindMainResponseInWorkingSet() {
BasicFindMainResponse(false, false);
}
void BlockFindMainResponseWithPolicyCheck() {
BasicFindMainResponse(true, true);
}
void BasicFindMainResponse(bool drop_from_working_set,
bool block_with_policy_check) {
PushNextTask(NewRunnableMethod(
this, &AppCacheStorageImplTest::Verify_BasicFindMainResponse));
policy_.can_load_return_value_ = !block_with_policy_check;
service()->set_appcache_policy(&policy_);
// Setup some preconditions. Create a complete cache with an entry
// in storage.
MakeCacheAndGroup(kManifestUrl, 1, 1, true);
cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::EXPLICIT, 1));
AppCacheDatabase::EntryRecord entry_record;
entry_record.cache_id = 1;
entry_record.url = kEntryUrl;
entry_record.flags = AppCacheEntry::EXPLICIT;
entry_record.response_id = 1;
EXPECT_TRUE(database()->InsertEntry(&entry_record));
// Optionally drop the cache/group pair from the working set.
if (drop_from_working_set) {
EXPECT_TRUE(cache_->HasOneRef());
cache_ = NULL;
EXPECT_TRUE(group_->HasOneRef());
group_ = NULL;
}
// Conduct the test.
storage()->FindResponseForMainRequest(kEntryUrl, delegate());
EXPECT_NE(kEntryUrl, delegate()->found_url_);
}
void Verify_BasicFindMainResponse() {
EXPECT_EQ(kManifestUrl, policy_.requested_manifest_url_);
if (policy_.can_load_return_value_) {
EXPECT_EQ(kEntryUrl, delegate()->found_url_);
EXPECT_EQ(kManifestUrl, delegate()->found_manifest_url_);
EXPECT_FALSE(delegate()->found_blocked_by_policy_);
EXPECT_EQ(1, delegate()->found_cache_id_);
EXPECT_EQ(1, delegate()->found_entry_.response_id());
EXPECT_TRUE(delegate()->found_entry_.IsExplicit());
EXPECT_FALSE(delegate()->found_fallback_entry_.has_response_id());
TestFinished();
} else {
Verify_FindNoMainResponse();
}
}
// BasicFindMainFallbackResponse -------------------------------
void BasicFindMainFallbackResponseInDatabase() {
BasicFindMainFallbackResponse(true);
}
void BasicFindMainFallbackResponseInWorkingSet() {
BasicFindMainFallbackResponse(false);
}
void BasicFindMainFallbackResponse(bool drop_from_working_set) {
PushNextTask(NewRunnableMethod(
this, &AppCacheStorageImplTest::Verify_BasicFindMainFallbackResponse));
// Setup some preconditions. Create a complete cache with a
// fallback namespace and entry.
MakeCacheAndGroup(kManifestUrl, 1, 1, true);
cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::FALLBACK, 1));
cache_->AddEntry(kEntryUrl2, AppCacheEntry(AppCacheEntry::FALLBACK, 2));
cache_->fallback_namespaces_.push_back(
FallbackNamespace(kFallbackNamespace2, kEntryUrl2));
cache_->fallback_namespaces_.push_back(
FallbackNamespace(kFallbackNamespace, kEntryUrl));
AppCacheDatabase::CacheRecord cache_record;
std::vector<AppCacheDatabase::EntryRecord> entries;
std::vector<AppCacheDatabase::FallbackNameSpaceRecord> fallbacks;
std::vector<AppCacheDatabase::OnlineWhiteListRecord> whitelists;
cache_->ToDatabaseRecords(group_,
&cache_record, &entries, &fallbacks, &whitelists);
EXPECT_TRUE(database()->InsertEntryRecords(entries));
EXPECT_TRUE(database()->InsertFallbackNameSpaceRecords(fallbacks));
EXPECT_TRUE(database()->InsertOnlineWhiteListRecords(whitelists));
if (drop_from_working_set) {
EXPECT_TRUE(cache_->HasOneRef());
cache_ = NULL;
EXPECT_TRUE(group_->HasOneRef());
group_ = NULL;
}
// Conduct the test. The test url is in both fallback namespace urls,
// but should match the longer of the two.
storage()->FindResponseForMainRequest(kFallbackTestUrl, delegate());
EXPECT_NE(kFallbackTestUrl, delegate()->found_url_);
}
void Verify_BasicFindMainFallbackResponse() {
EXPECT_EQ(kFallbackTestUrl, delegate()->found_url_);
EXPECT_EQ(kManifestUrl, delegate()->found_manifest_url_);
EXPECT_FALSE(delegate()->found_blocked_by_policy_);
EXPECT_EQ(1, delegate()->found_cache_id_);
EXPECT_FALSE(delegate()->found_entry_.has_response_id());
EXPECT_EQ(2, delegate()->found_fallback_entry_.response_id());
EXPECT_TRUE(delegate()->found_fallback_entry_.IsFallback());
TestFinished();
}
// FindMainResponseWithMultipleHits -------------------------------
void FindMainResponseWithMultipleHits() {
PushNextTask(NewRunnableMethod(this,
&AppCacheStorageImplTest::Verify_FindMainResponseWithMultipleHits));
// Setup some preconditions. Create 2 complete caches with an entry
// for the same url.
// The first cache, in the database but not in the working set.
MakeCacheAndGroup(kManifestUrl, 1, 1, true);
cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::EXPLICIT, 1));
AppCacheDatabase::EntryRecord entry_record;
entry_record.cache_id = 1;
entry_record.url = kEntryUrl;
entry_record.flags = AppCacheEntry::EXPLICIT;
entry_record.response_id = 1;
EXPECT_TRUE(database()->InsertEntry(&entry_record));
cache_ = NULL;
group_ = NULL;
// The second cache, in the database and working set.
MakeCacheAndGroup(kManifestUrl2, 2, 2, true);
cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::EXPLICIT, 2));
entry_record.cache_id = 2;
entry_record.url = kEntryUrl;
entry_record.flags = AppCacheEntry::EXPLICIT;
entry_record.response_id = 2;
EXPECT_TRUE(database()->InsertEntry(&entry_record));
// Conduct the test, we should find the response from the second cache
// since it's "in use".
storage()->FindResponseForMainRequest(kEntryUrl, delegate());
EXPECT_NE(kEntryUrl, delegate()->found_url_);
}
void Verify_FindMainResponseWithMultipleHits() {
EXPECT_EQ(kEntryUrl, delegate()->found_url_);
EXPECT_EQ(kManifestUrl2, delegate()->found_manifest_url_);
EXPECT_FALSE(delegate()->found_blocked_by_policy_);
EXPECT_EQ(2, delegate()->found_cache_id_);
EXPECT_EQ(2, delegate()->found_entry_.response_id());
EXPECT_TRUE(delegate()->found_entry_.IsExplicit());
EXPECT_FALSE(delegate()->found_fallback_entry_.has_response_id());
TestFinished();
}
// FindMainResponseExclusions -------------------------------
void FindMainResponseExclusionsInDatabase() {
FindMainResponseExclusions(true);
}
void FindMainResponseExclusionsInWorkingSet() {
FindMainResponseExclusions(false);
}
void FindMainResponseExclusions(bool drop_from_working_set) {
// Setup some preconditions. Create a complete cache with a
// foreign entry and an online namespace.
MakeCacheAndGroup(kManifestUrl, 1, 1, true);
cache_->AddEntry(kEntryUrl,
AppCacheEntry(AppCacheEntry::EXPLICIT | AppCacheEntry::FOREIGN, 1));
cache_->online_whitelist_namespaces_.push_back(kOnlineNamespace);
AppCacheDatabase::EntryRecord entry_record;
entry_record.cache_id = 1;
entry_record.url = kEntryUrl;
entry_record.flags = AppCacheEntry::EXPLICIT | AppCacheEntry::FOREIGN;
entry_record.response_id = 1;
EXPECT_TRUE(database()->InsertEntry(&entry_record));
AppCacheDatabase::OnlineWhiteListRecord whitelist_record;
whitelist_record.cache_id = 1;
whitelist_record.namespace_url = kOnlineNamespace;
EXPECT_TRUE(database()->InsertOnlineWhiteList(&whitelist_record));
if (drop_from_working_set) {
cache_ = NULL;
group_ = NULL;
}
// We should not find anything for the foreign entry.
PushNextTask(NewRunnableMethod(
this, &AppCacheStorageImplTest::Verify_NotFound, kEntryUrl, false));
storage()->FindResponseForMainRequest(kEntryUrl, delegate());
}
void Verify_NotFound(GURL expected_url, bool test_finished) {
EXPECT_EQ(expected_url, delegate()->found_url_);
EXPECT_TRUE(delegate()->found_manifest_url_.is_empty());
EXPECT_FALSE(delegate()->found_blocked_by_policy_);
EXPECT_EQ(kNoCacheId, delegate()->found_cache_id_);
EXPECT_EQ(kNoResponseId, delegate()->found_entry_.response_id());
EXPECT_EQ(kNoResponseId, delegate()->found_fallback_entry_.response_id());
EXPECT_EQ(0, delegate()->found_entry_.types());
EXPECT_EQ(0, delegate()->found_fallback_entry_.types());
if (!test_finished) {
// We should not find anything for the online namespace.
PushNextTask(NewRunnableMethod(this,
&AppCacheStorageImplTest::Verify_NotFound, kOnlineNamespace, true));
storage()->FindResponseForMainRequest(kOnlineNamespace, delegate());
return;
}
TestFinished();
}
// Test case helpers --------------------------------------------------
AppCacheService* service() {
return service_.get();
}
AppCacheStorageImpl* storage() {
return static_cast<AppCacheStorageImpl*>(service()->storage());
}
AppCacheDatabase* database() {
return storage()->database_;
}
MockStorageDelegate* delegate() {
return delegate_.get();
}
void MakeCacheAndGroup(
const GURL& manifest_url, int64 group_id, int64 cache_id,
bool add_to_database) {
group_ = new AppCacheGroup(service(), manifest_url, group_id);
cache_ = new AppCache(service(), cache_id);
cache_->set_complete(true);
group_->AddCache(cache_);
if (add_to_database) {
AppCacheDatabase::GroupRecord group_record;
group_record.group_id = group_id;
group_record.manifest_url = manifest_url;
group_record.origin = manifest_url.GetOrigin();
EXPECT_TRUE(database()->InsertGroup(&group_record));
AppCacheDatabase::CacheRecord cache_record;
cache_record.cache_id = cache_id;
cache_record.group_id = group_id;
cache_record.online_wildcard = false;
cache_record.update_time = kZeroTime;
EXPECT_TRUE(database()->InsertCache(&cache_record));
storage()->origins_with_groups_.insert(manifest_url.GetOrigin());
}
}
// Data members --------------------------------------------------
scoped_ptr<base::WaitableEvent> test_finished_event_;
std::stack<Task*> task_stack_;
MockAppCachePolicy policy_;
scoped_ptr<AppCacheService> service_;
scoped_ptr<MockStorageDelegate> delegate_;
scoped_refptr<AppCacheGroup> group_;
scoped_refptr<AppCache> cache_;
scoped_refptr<AppCache> cache2_;
};
TEST_F(AppCacheStorageImplTest, LoadCache_Miss) {
RunTestOnIOThread(&AppCacheStorageImplTest::LoadCache_Miss);
}
TEST_F(AppCacheStorageImplTest, LoadCache_NearHit) {
RunTestOnIOThread(&AppCacheStorageImplTest::LoadCache_NearHit);
}
TEST_F(AppCacheStorageImplTest, CreateGroupInEmptyOrigin) {
RunTestOnIOThread(&AppCacheStorageImplTest::CreateGroupInEmptyOrigin);
}
TEST_F(AppCacheStorageImplTest, CreateGroupInPopulatedOrigin) {
RunTestOnIOThread(&AppCacheStorageImplTest::CreateGroupInPopulatedOrigin);
}
TEST_F(AppCacheStorageImplTest, LoadGroupAndCache_FarHit) {
RunTestOnIOThread(&AppCacheStorageImplTest::LoadGroupAndCache_FarHit);
}
TEST_F(AppCacheStorageImplTest, StoreNewGroup) {
RunTestOnIOThread(&AppCacheStorageImplTest::StoreNewGroup);
}
TEST_F(AppCacheStorageImplTest, StoreExistingGroup) {
RunTestOnIOThread(&AppCacheStorageImplTest::StoreExistingGroup);
}
TEST_F(AppCacheStorageImplTest, StoreExistingGroupExistingCache) {
RunTestOnIOThread(&AppCacheStorageImplTest::StoreExistingGroupExistingCache);
}
TEST_F(AppCacheStorageImplTest, FailStoreGroup) {
RunTestOnIOThread(&AppCacheStorageImplTest::FailStoreGroup);
}
TEST_F(AppCacheStorageImplTest, MakeGroupObsolete) {
RunTestOnIOThread(&AppCacheStorageImplTest::MakeGroupObsolete);
}
TEST_F(AppCacheStorageImplTest, MarkEntryAsForeign) {
RunTestOnIOThread(&AppCacheStorageImplTest::MarkEntryAsForeign);
}
TEST_F(AppCacheStorageImplTest, MarkEntryAsForeignWithLoadInProgress) {
RunTestOnIOThread(
&AppCacheStorageImplTest::MarkEntryAsForeignWithLoadInProgress);
}
TEST_F(AppCacheStorageImplTest, FindNoMainResponse) {
RunTestOnIOThread(&AppCacheStorageImplTest::FindNoMainResponse);
}
TEST_F(AppCacheStorageImplTest, BasicFindMainResponseInDatabase) {
RunTestOnIOThread(
&AppCacheStorageImplTest::BasicFindMainResponseInDatabase);
}
TEST_F(AppCacheStorageImplTest, BasicFindMainResponseInWorkingSet) {
RunTestOnIOThread(
&AppCacheStorageImplTest::BasicFindMainResponseInWorkingSet);
}
TEST_F(AppCacheStorageImplTest, BlockFindMainResponseWithPolicyCheck) {
RunTestOnIOThread(
&AppCacheStorageImplTest::BlockFindMainResponseWithPolicyCheck);
}
TEST_F(AppCacheStorageImplTest, BasicFindMainFallbackResponseInDatabase) {
RunTestOnIOThread(
&AppCacheStorageImplTest::BasicFindMainFallbackResponseInDatabase);
}
TEST_F(AppCacheStorageImplTest, BasicFindMainFallbackResponseInWorkingSet) {
RunTestOnIOThread(
&AppCacheStorageImplTest::BasicFindMainFallbackResponseInWorkingSet);
}
TEST_F(AppCacheStorageImplTest, FindMainResponseWithMultipleHits) {
RunTestOnIOThread(
&AppCacheStorageImplTest::FindMainResponseWithMultipleHits);
}
TEST_F(AppCacheStorageImplTest, FindMainResponseExclusionsInDatabase) {
RunTestOnIOThread(
&AppCacheStorageImplTest::FindMainResponseExclusionsInDatabase);
}
TEST_F(AppCacheStorageImplTest, FindMainResponseExclusionsInWorkingSet) {
RunTestOnIOThread(
&AppCacheStorageImplTest::FindMainResponseExclusionsInWorkingSet);
}
// That's all folks!
} // namespace appcache
// AppCacheStorageImplTest is expected to always live longer than the
// runnable methods. This lets us call NewRunnableMethod on its instances.
DISABLE_RUNNABLE_METHOD_REFCOUNT(appcache::AppCacheStorageImplTest);