diff options
author | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-24 21:42:25 +0000 |
---|---|---|
committer | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-24 21:42:25 +0000 |
commit | 550890ec7f1cf968abc4544b170e97a67f919902 (patch) | |
tree | 632dec32bed5266837a2668a9225d303303e3160 /webkit/appcache | |
parent | ed249ab7916d438b0bdb650a796aaf709f6ae1ae (diff) | |
download | chromium_src-550890ec7f1cf968abc4544b170e97a67f919902.zip chromium_src-550890ec7f1cf968abc4544b170e97a67f919902.tar.gz chromium_src-550890ec7f1cf968abc4544b170e97a67f919902.tar.bz2 |
MockAppCacheStorage implemention
This is a quick and easy 'mock' implementation of the storage interface
that doesn't put anything to disk.
We simply add an extra reference to objects when they're put in storage,
and remove the extra reference when they are removed from storage.
Responses are never really removed from the in-memory disk cache.
Delegate callbacks are made asyncly to appropiately mimic what will
happen with a real disk-backed storage impl that involves IO on a
background thread.
This is for use in unit tests and to initially bring up the appcache
related layout tests.
TEST=mock_appcache_storage_unittest.cc
BUG=none
Review URL: http://codereview.chromium.org/300043
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@30017 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/appcache')
-rw-r--r-- | webkit/appcache/appcache_entry.h | 16 | ||||
-rw-r--r-- | webkit/appcache/appcache_group.h | 15 | ||||
-rw-r--r-- | webkit/appcache/appcache_response_unittest.cc | 44 | ||||
-rw-r--r-- | webkit/appcache/appcache_storage.h | 34 | ||||
-rw-r--r-- | webkit/appcache/appcache_storage_unittest.cc | 51 | ||||
-rw-r--r-- | webkit/appcache/mock_appcache_storage.cc | 267 | ||||
-rw-r--r-- | webkit/appcache/mock_appcache_storage.h | 51 | ||||
-rw-r--r-- | webkit/appcache/mock_appcache_storage_unittest.cc | 358 |
8 files changed, 689 insertions, 147 deletions
diff --git a/webkit/appcache/appcache_entry.h b/webkit/appcache/appcache_entry.h index 434568a..c57ab6b 100644 --- a/webkit/appcache/appcache_entry.h +++ b/webkit/appcache/appcache_entry.h @@ -5,6 +5,8 @@ #ifndef WEBKIT_APPCACHE_APPCACHE_ENTRY_H_ #define WEBKIT_APPCACHE_APPCACHE_ENTRY_H_ +#include "webkit/appcache/appcache_interfaces.h" + namespace appcache { // A cached entry is identified by a URL and is classified into one @@ -22,7 +24,13 @@ class AppCacheEntry { FALLBACK = 1 << 4, }; - explicit AppCacheEntry(int type) : types_(type) {} + AppCacheEntry() : types_(0), response_id_(kNoResponseId) {} + + explicit AppCacheEntry(int type) + : types_(type), response_id_(kNoResponseId) {} + + AppCacheEntry(int type, int64 response_id) + : types_(type), response_id_(response_id) {} int types() const { return types_; } void add_types(int added_types) { types_ |= added_types; } @@ -32,10 +40,12 @@ class AppCacheEntry { bool IsForeign() const { return (types_ & FOREIGN) != 0; } bool IsFallback() const { return (types_ & FALLBACK) != 0; } + int64 response_id() const { return response_id_; } + void set_response_id(int64 id) { response_id_ = id; } + private: int types_; - - // TODO(jennb): response storage key + int64 response_id_; }; } // namespace appcache diff --git a/webkit/appcache/appcache_group.h b/webkit/appcache/appcache_group.h index a5ff5fb..6f3c210 100644 --- a/webkit/appcache/appcache_group.h +++ b/webkit/appcache/appcache_group.h @@ -45,20 +45,18 @@ class AppCacheGroup : public base::RefCounted<AppCacheGroup> { void AddUpdateObserver(UpdateObserver* observer); void RemoveUpdateObserver(UpdateObserver* observer); - const GURL& manifest_url() { return manifest_url_; } + const GURL& manifest_url() const { return manifest_url_; } - bool is_obsolete() { return is_obsolete_; } + bool is_obsolete() const { return is_obsolete_; } void set_obsolete(bool value) { is_obsolete_ = value; } - AppCache* newest_complete_cache() { return newest_complete_cache_; } + AppCache* newest_complete_cache() const { return newest_complete_cache_; } void AddCache(AppCache* complete_cache); - void RemoveCache(AppCache* cache); + bool HasCache() const { return newest_complete_cache_ != NULL; } - bool HasCache() { return newest_complete_cache_ != NULL; } - - UpdateStatus update_status() { return update_status_; } + UpdateStatus update_status() const { return update_status_; } // Starts an update via update() javascript API. void StartUpdate() { @@ -79,13 +77,14 @@ class AppCacheGroup : public base::RefCounted<AppCacheGroup> { private: friend class AppCacheUpdateJob; friend class AppCacheUpdateJobTest; + friend class MockAppCacheStorage; // for old_caches() typedef std::vector<AppCache*> Caches; AppCacheUpdateJob* update_job() { return update_job_; } void SetUpdateStatus(UpdateStatus status); - const Caches& old_caches() { return old_caches_; } + const Caches& old_caches() const { return old_caches_; } GURL manifest_url_; UpdateStatus update_status_; diff --git a/webkit/appcache/appcache_response_unittest.cc b/webkit/appcache/appcache_response_unittest.cc index 854ac01..939086e 100644 --- a/webkit/appcache/appcache_response_unittest.cc +++ b/webkit/appcache/appcache_response_unittest.cc @@ -287,46 +287,6 @@ class AppCacheResponseTest : public testing::Test { // Most of the individual tests involve multiple async steps. Each test // is delineated with a section header. - // DelegateReferences ------------------------------------------------------- - // TODO(michaeln): maybe this one belongs in appcache_storage_unittest.cc - void DelegateReferences() { - typedef scoped_refptr<AppCacheStorage::DelegateReference> - ScopedDelegateReference; - MockStorageDelegate delegate(this); - ScopedDelegateReference delegate_reference1; - ScopedDelegateReference delegate_reference2; - - EXPECT_FALSE(service_->storage()->GetDelegateReference(&delegate)); - - delegate_reference1 = - service_->storage()->GetOrCreateDelegateReference(&delegate); - EXPECT_TRUE(delegate_reference1.get()); - EXPECT_TRUE(delegate_reference1->HasOneRef()); - EXPECT_TRUE(service_->storage()->GetDelegateReference(&delegate)); - EXPECT_EQ(&delegate, - service_->storage()->GetDelegateReference(&delegate)->delegate); - EXPECT_EQ(service_->storage()->GetDelegateReference(&delegate), - service_->storage()->GetOrCreateDelegateReference(&delegate)); - delegate_reference1 = NULL; - EXPECT_FALSE(service_->storage()->GetDelegateReference(&delegate)); - - delegate_reference1 = - service_->storage()->GetOrCreateDelegateReference(&delegate); - service_->storage()->CancelDelegateCallbacks(&delegate); - EXPECT_TRUE(delegate_reference1.get()); - EXPECT_TRUE(delegate_reference1->HasOneRef()); - EXPECT_FALSE(delegate_reference1->delegate); - EXPECT_FALSE(service_->storage()->GetDelegateReference(&delegate)); - - delegate_reference2 = - service_->storage()->GetOrCreateDelegateReference(&delegate); - EXPECT_TRUE(delegate_reference2.get()); - EXPECT_TRUE(delegate_reference2->HasOneRef()); - EXPECT_EQ(&delegate, delegate_reference2->delegate); - EXPECT_NE(delegate_reference1.get(), delegate_reference2.get()); - - TestFinished(); - } // ReadNonExistentResponse ------------------------------------------- void ReadNonExistentResponse() { @@ -680,10 +640,6 @@ class AppCacheResponseTest : public testing::Test { // static scoped_ptr<base::Thread> AppCacheResponseTest::io_thread_; -TEST_F(AppCacheResponseTest, DelegateReferences) { - RunTestOnIOThread(&AppCacheResponseTest::DelegateReferences); -} - TEST_F(AppCacheResponseTest, ReadNonExistentResponse) { RunTestOnIOThread(&AppCacheResponseTest::ReadNonExistentResponse); } diff --git a/webkit/appcache/appcache_storage.h b/webkit/appcache/appcache_storage.h index 912bdcb..c221054 100644 --- a/webkit/appcache/appcache_storage.h +++ b/webkit/appcache/appcache_storage.h @@ -12,6 +12,7 @@ #include "base/basictypes.h" #include "base/ref_counted.h" #include "net/base/net_errors.h" +#include "testing/gtest/include/gtest/gtest_prod.h" #include "webkit/appcache/appcache_response.h" #include "webkit/appcache/appcache_working_set.h" @@ -20,6 +21,7 @@ class GURL; namespace appcache { class AppCache; +class AppCacheEntry; class AppCacheGroup; class AppCacheService; @@ -41,18 +43,18 @@ class AppCacheStorage { virtual void OnGroupAndNewestCacheStored( AppCacheGroup* group, bool success) {} - // If the update fails, success will be false. - virtual void OnGroupMarkedAsObsolete(GURL& manifest_url, bool success) {} + // If the operation fails, success will be false. + virtual void OnGroupMadeObsolete(AppCacheGroup* group, bool success) {} // If a load fails the 'response_info' will be NULL. virtual void OnResponseInfoLoaded( AppCacheResponseInfo* response_info, int64 response_id) {} - // If no response is found, response_id will be kNoResponseId. + // If no response is found, entry.response_id() will be kNoResponseId. // If a response is found, the cache id and manifest url of the // containing cache and group are also returned. virtual void OnMainResponseFound( - const GURL& url, int64 response_id, bool is_fallback, + const GURL& url, const AppCacheEntry& entry, int64 cache_id, const GURL& mainfest_url) {} }; @@ -106,7 +108,7 @@ class AppCacheStorage { // Schedules a task to update persistent storage and doom the group and all // related caches and responses for deletion. Upon completion the in-memory // instance is marked as obsolete and the delegate callback is called. - virtual void MarkGroupAsObsolete( + virtual void MakeGroupObsolete( AppCacheGroup* group, Delegate* delegate) = 0; // Cancels all pending callbacks for the delegate. The delegate callbacks @@ -151,6 +153,7 @@ class AppCacheStorage { protected: friend class AppCacheResponseTest; + friend class AppCacheStorageTest; // Helper to call a collection of delegates. #define FOR_EACH_DELEGATE(delegates, func_and_args) \ @@ -289,29 +292,10 @@ class AppCacheStorage { // The set of last ids must be retrieved from storage prior to being used. static const int64 kUnitializedId = -1; + FRIEND_TEST(AppCacheStorageTest, DelegateReferences); DISALLOW_COPY_AND_ASSIGN(AppCacheStorage); }; -// TODO(michaeln): Maybe? -class AppCacheStoredItem { - public: - bool is_doomed() const { return is_doomed_; } - bool is_stored() const { return is_stored_; } - - protected: - AppCacheStoredItem() : is_doomed_(false), is_stored_(false) {} - - private: - friend class AppCacheStorage; - friend class MockAppCacheStorage; - - void set_is_doomed(bool b) { is_doomed_ = b; } - void set_is_stored(bool b) { is_stored_ = b; } - - bool is_doomed_; - bool is_stored_; -}; - } // namespace appcache #endif // WEBKIT_APPCACHE_APPCACHE_STORAGE_H_ diff --git a/webkit/appcache/appcache_storage_unittest.cc b/webkit/appcache/appcache_storage_unittest.cc index 6e06265..7025e75 100644 --- a/webkit/appcache/appcache_storage_unittest.cc +++ b/webkit/appcache/appcache_storage_unittest.cc @@ -2,19 +2,24 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/message_loop.h" #include "testing/gtest/include/gtest/gtest.h" #include "webkit/appcache/appcache.h" #include "webkit/appcache/appcache_group.h" -#include "webkit/appcache/appcache_storage.h" #include "webkit/appcache/appcache_response.h" +#include "webkit/appcache/appcache_storage.h" #include "webkit/appcache/mock_appcache_service.h" namespace appcache { class AppCacheStorageTest : public testing::Test { + public: + class MockStorageDelegate : public AppCacheStorage::Delegate { + public: + }; }; -TEST(AppCacheStorageTest, AddRemoveCache) { +TEST_F(AppCacheStorageTest, AddRemoveCache) { MockAppCacheService service; scoped_refptr<AppCache> cache = new AppCache(&service, 111); @@ -30,7 +35,7 @@ TEST(AppCacheStorageTest, AddRemoveCache) { dummy.storage()->working_set()->RemoveCache(cache); } -TEST(AppCacheStorageTest, AddRemoveGroup) { +TEST_F(AppCacheStorageTest, AddRemoveGroup) { MockAppCacheService service; scoped_refptr<AppCacheGroup> group = new AppCacheGroup(&service, GURL::EmptyGURL()); @@ -47,7 +52,7 @@ TEST(AppCacheStorageTest, AddRemoveGroup) { dummy.storage()->working_set()->RemoveGroup(group); } -TEST(AppCacheStorageTest, AddRemoveResponseInfo) { +TEST_F(AppCacheStorageTest, AddRemoveResponseInfo) { MockAppCacheService service; scoped_refptr<AppCacheResponseInfo> info = new AppCacheResponseInfo(&service, 111, new net::HttpResponseInfo); @@ -64,4 +69,42 @@ TEST(AppCacheStorageTest, AddRemoveResponseInfo) { dummy.storage()->working_set()->RemoveResponseInfo(info); } +TEST_F(AppCacheStorageTest, DelegateReferences) { + typedef scoped_refptr<AppCacheStorage::DelegateReference> + ScopedDelegateReference; + MockAppCacheService service; + MockStorageDelegate delegate; + ScopedDelegateReference delegate_reference1; + ScopedDelegateReference delegate_reference2; + + EXPECT_FALSE(service.storage()->GetDelegateReference(&delegate)); + + delegate_reference1 = + service.storage()->GetOrCreateDelegateReference(&delegate); + EXPECT_TRUE(delegate_reference1.get()); + EXPECT_TRUE(delegate_reference1->HasOneRef()); + EXPECT_TRUE(service.storage()->GetDelegateReference(&delegate)); + EXPECT_EQ(&delegate, + service.storage()->GetDelegateReference(&delegate)->delegate); + EXPECT_EQ(service.storage()->GetDelegateReference(&delegate), + service.storage()->GetOrCreateDelegateReference(&delegate)); + delegate_reference1 = NULL; + EXPECT_FALSE(service.storage()->GetDelegateReference(&delegate)); + + delegate_reference1 = + service.storage()->GetOrCreateDelegateReference(&delegate); + service.storage()->CancelDelegateCallbacks(&delegate); + EXPECT_TRUE(delegate_reference1.get()); + EXPECT_TRUE(delegate_reference1->HasOneRef()); + EXPECT_FALSE(delegate_reference1->delegate); + EXPECT_FALSE(service.storage()->GetDelegateReference(&delegate)); + + delegate_reference2 = + service.storage()->GetOrCreateDelegateReference(&delegate); + EXPECT_TRUE(delegate_reference2.get()); + EXPECT_TRUE(delegate_reference2->HasOneRef()); + EXPECT_EQ(&delegate, delegate_reference2->delegate); + EXPECT_NE(delegate_reference1.get(), delegate_reference2.get()); +} + } // namespace appcache diff --git a/webkit/appcache/mock_appcache_storage.cc b/webkit/appcache/mock_appcache_storage.cc index 3144179..3c626ec 100644 --- a/webkit/appcache/mock_appcache_storage.cc +++ b/webkit/appcache/mock_appcache_storage.cc @@ -5,86 +5,89 @@ #include "webkit/appcache/mock_appcache_storage.h" #include "base/logging.h" +#include "base/message_loop.h" #include "base/ref_counted.h" +#include "base/stl_util-inl.h" #include "webkit/appcache/appcache.h" #include "webkit/appcache/appcache_entry.h" #include "webkit/appcache/appcache_group.h" #include "webkit/appcache/appcache_response.h" +// This is a quick and easy 'mock' implementation of the storage interface +// that doesn't put anything to disk. +// +// We simply add an extra reference to objects when they're put in storage, +// and remove the extra reference when they are removed from storage. +// Responses are never really removed from the in-memory disk cache. +// Delegate callbacks are made asyncly to appropiately mimic what will +// happen with a real disk-backed storage impl that involves IO on a +// background thread. + namespace appcache { MockAppCacheStorage::MockAppCacheStorage(AppCacheService* service) - : AppCacheStorage(service) { + : AppCacheStorage(service), + ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { last_cache_id_ = 0; last_entry_id_ = 0; last_group_id_ = 0; last_response_id_ = 0; } +MockAppCacheStorage::~MockAppCacheStorage() { + STLDeleteElements(&pending_tasks_); +} + void MockAppCacheStorage::LoadCache(int64 id, Delegate* delegate) { + DCHECK(delegate); AppCache* cache = working_set_.GetCache(id); - if (cache->HasOneRef()) { - // TODO(michaeln): make the load look async + if (ShouldCacheLoadAppearAsync(cache)) { + ScheduleTask(method_factory_.NewRunnableMethod( + &MockAppCacheStorage::ProcessLoadCache, + id, GetOrCreateDelegateReference(delegate))); + return; } - delegate->OnCacheLoaded(cache, id); + ProcessLoadCache(id, GetOrCreateDelegateReference(delegate)); } void MockAppCacheStorage::LoadOrCreateGroup( const GURL& manifest_url, Delegate* delegate) { - scoped_refptr<AppCacheGroup> group = working_set_.GetGroup(manifest_url); - if (!group.get()) { - group = new AppCacheGroup(service_, manifest_url); - DCHECK(working_set_.GetGroup(manifest_url)); + DCHECK(delegate); + AppCacheGroup* group = working_set_.GetGroup(manifest_url); + if (ShouldGroupLoadAppearAsync(group)) { + ScheduleTask(method_factory_.NewRunnableMethod( + &MockAppCacheStorage::ProcessLoadOrCreateGroup, + manifest_url, GetOrCreateDelegateReference(delegate))); + return; } - // TODO(michaeln): make the load look async if all of the groups - // caches only have one ref - delegate->OnGroupLoaded(group.get(), manifest_url); + ProcessLoadOrCreateGroup( + manifest_url, GetOrCreateDelegateReference(delegate)); } void MockAppCacheStorage::StoreGroupAndNewestCache( AppCacheGroup* group, Delegate* delegate) { + DCHECK(group && delegate); DCHECK(group->newest_complete_cache()); - // TODO(michaeln): write me - // 'store' the group + newest - // 'unstore' the old caches - // figure out which responses can be doomed and doom them - // OldRepsonses - NewestResponse == ToBeDoomed - - // TODO(michaeln): Make this appear async - //AddStoredGroup(group); - //AddStoredCache(group->newest_complete_cache()); - // - //foreach(group->old_caches()) - // RemoveStoredCache(old_cache); - //std::set<int64> doomed_responses_ = responses from old caches - //std::set<int64> needed_responses_ = responses from newest cache - //foreach(needed_responses_) - // doomed_responses_.remove(needed_response_); - //DoomResponses(group->manifest_url(), doomed_responses_); - - delegate->OnGroupAndNewestCacheStored(group, false); + // Always make this operation look async. + ScheduleTask(method_factory_.NewRunnableMethod( + &MockAppCacheStorage::ProcessStoreGroupAndNewestCache, + group, group->newest_complete_cache(), + GetOrCreateDelegateReference(delegate))); } void MockAppCacheStorage::FindResponseForMainRequest( const GURL& url, Delegate* delegate) { - // TODO(michaeln): write me - // - //foreach(stored_group) { - // if (group->manifest_url()->origin() != url.GetOrigin()) - // continue; - // - // look for an entry - // look for a fallback namespace - // look for a online namespace - //} - delegate->OnMainResponseFound( - url, kNoResponseId, false, kNoCacheId, GURL::EmptyGURL()); + DCHECK(delegate); + + // Always make this operation look async. + ScheduleTask(method_factory_.NewRunnableMethod( + &MockAppCacheStorage::ProcessFindResponseForMainRequest, + url, GetOrCreateDelegateReference(delegate))); } void MockAppCacheStorage::MarkEntryAsForeign( const GURL& entry_url, int64 cache_id) { - // Update the working set. AppCache* cache = working_set_.GetCache(cache_id); if (cache) { AppCacheEntry* entry = cache->GetEntry(entry_url); @@ -96,14 +99,14 @@ void MockAppCacheStorage::MarkEntryAsForeign( // being loaded be sure to update the memory cache upon load completion. } -void MockAppCacheStorage::MarkGroupAsObsolete( +void MockAppCacheStorage::MakeGroupObsolete( AppCacheGroup* group, Delegate* delegate) { - // TODO(michaeln): write me - // remove from working_set - // remove from storage - // doom things - // group->set_obsolete(true); - // TODO(michaeln): Make this appear async + DCHECK(group && delegate); + + // Always make this method look async. + ScheduleTask(method_factory_.NewRunnableMethod( + &MockAppCacheStorage::ProcessMakeGroupObsolete, + group, GetOrCreateDelegateReference(delegate))); } AppCacheResponseReader* MockAppCacheStorage::CreateResponseReader( @@ -118,31 +121,185 @@ AppCacheResponseWriter* MockAppCacheStorage::CreateResponseWriter( void MockAppCacheStorage::DoomResponses( const GURL& manifest_url, const std::vector<int64>& response_ids) { - // We don't bother with deleting responses from mock storage. + // We don't bother with actually removing responses from the disk-cache, + // just keep track of which ids have been doomed. + std::vector<int64>::const_iterator it = response_ids.begin(); + while (it != response_ids.end()) { + doomed_response_ids_.insert(*it); + ++it; + } +} + +void MockAppCacheStorage::ProcessLoadCache( + int64 id, scoped_refptr<DelegateReference> delegate_ref) { + AppCache* cache = working_set_.GetCache(id); + if (delegate_ref->delegate) + delegate_ref->delegate->OnCacheLoaded(cache, id); +} + +void MockAppCacheStorage::ProcessLoadOrCreateGroup( + const GURL& manifest_url, scoped_refptr<DelegateReference> delegate_ref) { + scoped_refptr<AppCacheGroup> group = working_set_.GetGroup(manifest_url); + + // Newly created groups are not put in the stored_groups collection + // until StoreGroupAndNewestCache is called. + if (!group) + group = new AppCacheGroup(service_, manifest_url); + + if (delegate_ref->delegate) + delegate_ref->delegate->OnGroupLoaded(group, manifest_url); +} + +void MockAppCacheStorage::ProcessStoreGroupAndNewestCache( + scoped_refptr<AppCacheGroup> group, + scoped_refptr<AppCache> newest_cache, + scoped_refptr<DelegateReference> delegate_ref) { + DCHECK(group->newest_complete_cache() == newest_cache.get()); + + AddStoredGroup(group); + AddStoredCache(group->newest_complete_cache()); + + // Copy the collection prior to removal, on final release + // of a cache the group's collection will change. + AppCacheGroup::Caches copy = group->old_caches(); + RemoveStoredCaches(copy); + + if (delegate_ref->delegate) + delegate_ref->delegate->OnGroupAndNewestCacheStored(group, true); + + // We don't bother with removing responses from 'mock' storage + // TODO(michaeln): for 'real' storage... + // std::set<int64> doomed_responses_ = responses from old caches + // std::set<int64> needed_responses_ = responses from newest cache + // foreach(needed_responses_) + // doomed_responses_.remove(needed_response_); + // DoomResponses(group->manifest_url(), doomed_responses_); +} + +void MockAppCacheStorage::ProcessFindResponseForMainRequest( + const GURL& url, scoped_refptr<DelegateReference> delegate_ref) { + // TODO(michaeln): write me when doing AppCacheRequestHandler + // foreach(stored_group) { + // if (group->manifest_url()->origin() != url.GetOrigin()) + // continue; + // look for an entry + // look for a fallback namespace + // look for a online namespace + // } + if (delegate_ref->delegate) { + delegate_ref->delegate->OnMainResponseFound( + url, AppCacheEntry(), kNoCacheId, GURL()); + } +} + +void MockAppCacheStorage::ProcessMakeGroupObsolete( + scoped_refptr<AppCacheGroup> group, + scoped_refptr<DelegateReference> delegate_ref) { + RemoveStoredGroup(group); + if (group->newest_complete_cache()) + RemoveStoredCache(group->newest_complete_cache()); + + // Copy the collection prior to removal, on final release + // of a cache the group's collection will change. + AppCacheGroup::Caches copy = group->old_caches(); + RemoveStoredCaches(copy); + + group->set_obsolete(true); + + if (delegate_ref->delegate) + delegate_ref->delegate->OnGroupMadeObsolete(group, true); +} + +void MockAppCacheStorage::ScheduleTask(Task* task) { + pending_tasks_.push_back(task); + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod( + &MockAppCacheStorage::RunOnePendingTask)); +} + +void MockAppCacheStorage::RunOnePendingTask() { + DCHECK(!pending_tasks_.empty()); + Task* task = pending_tasks_.front(); + pending_tasks_.pop_front(); + task->Run(); + delete task; } void MockAppCacheStorage::AddStoredCache(AppCache* cache) { -// cache->set_is_stored(true); int64 cache_id = cache->cache_id(); if (stored_caches_.find(cache_id) == stored_caches_.end()) stored_caches_.insert(StoredCacheMap::value_type(cache_id, cache)); } void MockAppCacheStorage::RemoveStoredCache(AppCache* cache) { -// cache->set_is_stored(false); + // Do not remove from the working set, active caches are still usable + // and may be looked up by id until they fall out of use. stored_caches_.erase(cache->cache_id()); } +void MockAppCacheStorage::RemoveStoredCaches( + const AppCacheGroup::Caches& caches) { + AppCacheGroup::Caches::const_iterator it = caches.begin(); + while (it != caches.end()) { + RemoveStoredCache(*it); + ++it; + } +} + void MockAppCacheStorage::AddStoredGroup(AppCacheGroup* group) { -// group->set_is_stored(true); const GURL& url = group->manifest_url(); if (stored_groups_.find(url) == stored_groups_.end()) stored_groups_.insert(StoredGroupMap::value_type(url, group)); } void MockAppCacheStorage::RemoveStoredGroup(AppCacheGroup* group) { -// group->set_is_stored(false); + // Also remove from the working set, caches for an 'obsolete' group + // may linger in use, but the group itself cannot be looked up by + // 'manifest_url' in the working set any longer. + working_set()->RemoveGroup(group); stored_groups_.erase(group->manifest_url()); } +bool MockAppCacheStorage::ShouldGroupLoadAppearAsync( + const AppCacheGroup* group) { + // We'll have to query the database to see if a group for the + // manifest_url exists on disk. So return true for async. + if (!group) + return true; + + // Groups without a newest cache can't have been put to disk yet, so + // we can synchronously return a reference we have in the working set. + if (!group->newest_complete_cache()) + return false; + + // The LoadGroup interface implies also loading the newest cache, so + // if loading the newest cache should appear async, so too must the + // loading of this group. + if (ShouldCacheLoadAppearAsync(group->newest_complete_cache())) + return true; + + + // If any of the old caches are "in use", then the group must also + // be memory resident and not require async loading. + const AppCacheGroup::Caches& old_caches = group->old_caches(); + AppCacheGroup::Caches::const_iterator it = old_caches.begin(); + while (it != old_caches.end()) { + // "in use" caches don't require async loading + if (!ShouldCacheLoadAppearAsync(*it)) + return false; + ++it; + } + + return true; +} + +bool MockAppCacheStorage::ShouldCacheLoadAppearAsync(const AppCache* cache) { + if (!cache) + return true; + + // If the 'stored' ref is the only ref, real storage will have to load from + // the database. + return IsCacheStored(cache) && cache->HasOneRef(); +} + } // namespace appcache diff --git a/webkit/appcache/mock_appcache_storage.h b/webkit/appcache/mock_appcache_storage.h index 8efb7d9..ca6bd70 100644 --- a/webkit/appcache/mock_appcache_storage.h +++ b/webkit/appcache/mock_appcache_storage.h @@ -5,10 +5,12 @@ #ifndef WEBKIT_APPCACHE_MOCK_APPCACHE_STORAGE_H_ #define WEBKIT_APPCACHE_MOCK_APPCACHE_STORAGE_H_ +#include <deque> #include <map> #include "base/hash_tables.h" #include "base/scoped_ptr.h" +#include "base/task.h" #include "net/disk_cache/disk_cache.h" #include "webkit/appcache/appcache.h" #include "webkit/appcache/appcache_group.h" @@ -23,13 +25,15 @@ namespace appcache { class MockAppCacheStorage : public AppCacheStorage { public: explicit MockAppCacheStorage(AppCacheService* service); + virtual ~MockAppCacheStorage(); + virtual void LoadCache(int64 id, Delegate* delegate); virtual void LoadOrCreateGroup(const GURL& manifest_url, Delegate* delegate); virtual void StoreGroupAndNewestCache( AppCacheGroup* group, Delegate* delegate); virtual void FindResponseForMainRequest(const GURL& url, Delegate* delegate); virtual void MarkEntryAsForeign(const GURL& entry_url, int64 cache_id); - virtual void MarkGroupAsObsolete(AppCacheGroup* group, Delegate* delegate); + virtual void MakeGroupObsolete(AppCacheGroup* group, Delegate* delegate); virtual AppCacheResponseReader* CreateResponseReader( const GURL& manifest_url, int64 response_id); virtual AppCacheResponseWriter* CreateResponseWriter(const GURL& origin); @@ -39,20 +43,40 @@ class MockAppCacheStorage : public AppCacheStorage { private: typedef base::hash_map<int64, scoped_refptr<AppCache> > StoredCacheMap; typedef std::map<GURL, scoped_refptr<AppCacheGroup> > StoredGroupMap; + typedef std::set<int64> DoomedResponseIds; + + void ProcessLoadCache( + int64 id, scoped_refptr<DelegateReference> delegate_ref); + void ProcessLoadOrCreateGroup( + const GURL& manifest_url, scoped_refptr<DelegateReference> delegate_ref); + void ProcessStoreGroupAndNewestCache( + scoped_refptr<AppCacheGroup> group, scoped_refptr<AppCache> newest_cache, + scoped_refptr<DelegateReference> delegate_ref); + void ProcessMakeGroupObsolete( + scoped_refptr<AppCacheGroup> group, + scoped_refptr<DelegateReference> delegate_ref); + void ProcessFindResponseForMainRequest( + const GURL& url, scoped_refptr<DelegateReference> delegate_ref); + + void ScheduleTask(Task* task); + void RunOnePendingTask(); void AddStoredCache(AppCache* cache); void RemoveStoredCache(AppCache* cache); - AppCache* GetStoredCache(int64 id) { - StoredCacheMap::iterator it = stored_caches_.find(id); - return (it != stored_caches_.end()) ? it->second : NULL; + void RemoveStoredCaches(const AppCacheGroup::Caches& caches); + bool IsCacheStored(const AppCache* cache) { + return stored_caches_.find(cache->cache_id()) != stored_caches_.end(); } void AddStoredGroup(AppCacheGroup* group); void RemoveStoredGroup(AppCacheGroup* group); - AppCacheGroup* GetStoredGroup(const GURL& manifest_url) { - StoredGroupMap::iterator it = stored_groups_.find(manifest_url); - return (it != stored_groups_.end()) ? it->second : NULL; - } + + // These helpers determine when certain operations should complete + // asynchronously vs synchronously to faithfully mimic, or mock, + // the behavior of the real implemenation of the AppCacheStorage + // interface. + bool ShouldGroupLoadAppearAsync(const AppCacheGroup* group); + bool ShouldCacheLoadAppearAsync(const AppCache* cache); // Lazily constructed in-memory disk cache. disk_cache::Backend* disk_cache() { @@ -65,7 +89,18 @@ class MockAppCacheStorage : public AppCacheStorage { StoredCacheMap stored_caches_; StoredGroupMap stored_groups_; + DoomedResponseIds doomed_response_ids_; scoped_ptr<disk_cache::Backend> disk_cache_; + std::deque<Task*> pending_tasks_; + ScopedRunnableMethodFactory<MockAppCacheStorage> method_factory_; + + FRIEND_TEST(MockAppCacheStorageTest, CreateGroup); + FRIEND_TEST(MockAppCacheStorageTest, LoadCache_FarHit); + FRIEND_TEST(MockAppCacheStorageTest, LoadGroupAndCache_FarHit); + FRIEND_TEST(MockAppCacheStorageTest, MakeGroupObsolete); + FRIEND_TEST(MockAppCacheStorageTest, StoreNewGroup); + FRIEND_TEST(MockAppCacheStorageTest, StoreExistingGroup); + DISALLOW_COPY_AND_ASSIGN(MockAppCacheStorage); }; } // namespace appcache diff --git a/webkit/appcache/mock_appcache_storage_unittest.cc b/webkit/appcache/mock_appcache_storage_unittest.cc new file mode 100644 index 0000000..60cd801 --- /dev/null +++ b/webkit/appcache/mock_appcache_storage_unittest.cc @@ -0,0 +1,358 @@ +// Copyright (c) 2009 The Chromium Authos. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/appcache/appcache.h" +#include "webkit/appcache/appcache_group.h" +#include "webkit/appcache/appcache_response.h" +#include "webkit/appcache/appcache_storage.h" +#include "webkit/appcache/mock_appcache_service.h" + +namespace appcache { + +class MockAppCacheStorageTest : public testing::Test { + public: + class MockStorageDelegate : public AppCacheStorage::Delegate { + public: + explicit MockStorageDelegate() + : loaded_cache_id_(0), stored_group_success_(false), + obsoleted_success_(false), found_cache_id_(kNoCacheId) { + } + + void OnCacheLoaded(AppCache* cache, int64 cache_id) { + loaded_cache_ = cache; + loaded_cache_id_ = cache_id; + } + + void OnGroupLoaded(AppCacheGroup* group, const GURL& manifest_url) { + loaded_group_ = group; + loaded_manifest_url_ = manifest_url; + } + + void OnGroupAndNewestCacheStored(AppCacheGroup* group, bool success) { + stored_group_ = group; + stored_group_success_ = success; + } + + void OnGroupMadeObsolete(AppCacheGroup* group, bool success) { + obsoleted_group_ = group; + obsoleted_success_ = success; + } + + void OnMainResponseFound(const GURL& url, const AppCacheEntry& entry, + int64 cache_id, const GURL& manifest_url) { + found_url_ = url; + found_entry_ = entry; + found_cache_id_ = cache_id; + found_manifest_url_ = manifest_url; + } + + scoped_refptr<AppCache> loaded_cache_; + int64 loaded_cache_id_; + scoped_refptr<AppCacheGroup> loaded_group_; + GURL loaded_manifest_url_; + scoped_refptr<AppCacheGroup> stored_group_; + bool stored_group_success_; + scoped_refptr<AppCacheGroup> obsoleted_group_; + bool obsoleted_success_; + GURL found_url_; + AppCacheEntry found_entry_; + int64 found_cache_id_; + GURL found_manifest_url_; + }; +}; + + +TEST_F(MockAppCacheStorageTest, LoadCache_Miss) { + // Attempt to load a cache that doesn't exist. Should + // complete asyncly. + MockAppCacheService service; + MockStorageDelegate delegate; + service.storage()->LoadCache(111, &delegate); + EXPECT_NE(111, delegate.loaded_cache_id_); + MessageLoop::current()->RunAllPending(); // Do async task execution. + EXPECT_EQ(111, delegate.loaded_cache_id_); + EXPECT_FALSE(delegate.loaded_cache_); +} + +TEST_F(MockAppCacheStorageTest, LoadCache_NearHit) { + // Attempt to load a cache that is currently in use + // and does not require loading from disk. This + // load should complete syncly. + MockAppCacheService service; + + // Setup some preconditions. Make an 'unstored' cache for + // us to load. The ctor should put it in the working set. + int64 cache_id = service.storage()->NewCacheId(); + scoped_refptr<AppCache> cache = new AppCache(&service, cache_id); + + // Conduct the test. + MockStorageDelegate delegate; + service.storage()->LoadCache(cache_id, &delegate); + EXPECT_EQ(cache_id, delegate.loaded_cache_id_); + EXPECT_EQ(cache.get(), delegate.loaded_cache_.get()); +} + +TEST_F(MockAppCacheStorageTest, CreateGroup) { + // Attempt to load/create a group that doesn't exist. + // Should complete asyncly. + MockAppCacheService service; + MockAppCacheStorage* storage = + reinterpret_cast<MockAppCacheStorage*>(service.storage()); + MockStorageDelegate delegate; + GURL manifest_url("http://blah/"); + service.storage()->LoadOrCreateGroup(manifest_url, &delegate); + EXPECT_NE(manifest_url, delegate.loaded_manifest_url_); + EXPECT_FALSE(delegate.loaded_group_.get()); + MessageLoop::current()->RunAllPending(); // Do async task execution. + EXPECT_EQ(manifest_url, delegate.loaded_manifest_url_); + EXPECT_TRUE(delegate.loaded_group_.get()); + EXPECT_TRUE(delegate.loaded_group_->HasOneRef()); + EXPECT_FALSE(delegate.loaded_group_->newest_complete_cache()); + EXPECT_TRUE(storage->stored_groups_.empty()); +} + +TEST_F(MockAppCacheStorageTest, LoadGroup_NearHit) { + // Attempt to load a group that is currently in use + // and does not require loading from disk. This + // load should complete syncly. + MockAppCacheService service; + MockStorageDelegate delegate; + + // Setup some preconditions. Create a group that appears + // to be "unstored" and "currently in use". + GURL manifest_url("http://blah/"); + service.storage()->LoadOrCreateGroup(manifest_url, &delegate); + MessageLoop::current()->RunAllPending(); // Do async task execution. + EXPECT_EQ(manifest_url, delegate.loaded_manifest_url_); + EXPECT_TRUE(delegate.loaded_group_.get()); + + // Reset our delegate, and take a reference to the new group. + scoped_refptr<AppCacheGroup> group; + group.swap(delegate.loaded_group_); + delegate.loaded_manifest_url_ = GURL(); + + // Conduct the test. + service.storage()->LoadOrCreateGroup(manifest_url, &delegate); + EXPECT_EQ(manifest_url, delegate.loaded_manifest_url_); + EXPECT_EQ(group.get(), delegate.loaded_group_.get()); +} + +TEST_F(MockAppCacheStorageTest, LoadGroupAndCache_FarHit) { + // Attempt to load a cache that is not currently in use + // and does require loading from disk. This + // load should complete asyncly. + MockAppCacheService service; + MockAppCacheStorage* storage = + reinterpret_cast<MockAppCacheStorage*>(service.storage()); + + // Setup some preconditions. Create a group and newest cache that + // appears to be "stored" and "not currently in use". + GURL manifest_url("http://blah/"); + scoped_refptr<AppCacheGroup> group = + new AppCacheGroup(&service, manifest_url); + int64 cache_id = storage->NewCacheId(); + scoped_refptr<AppCache> cache = new AppCache(&service, cache_id); + cache->set_complete(true); + group->AddCache(cache); + storage->AddStoredGroup(group); + storage->AddStoredCache(cache); + + // Drop the references from above so the only refs to these + // objects are from within the storage class. This is to make + // these objects appear as "not currently in use". + AppCache* cache_ptr = cache.get(); + AppCacheGroup* group_ptr = group.get(); + cache = NULL; + group = NULL; + + // Setup a delegate to receive completion callbacks. + MockStorageDelegate delegate; + + // Conduct the cache load test. + EXPECT_NE(cache_id, delegate.loaded_cache_id_); + EXPECT_NE(cache_ptr, delegate.loaded_cache_.get()); + storage->LoadCache(cache_id, &delegate); + EXPECT_NE(cache_id, delegate.loaded_cache_id_); + EXPECT_NE(cache_ptr, delegate.loaded_cache_.get()); + MessageLoop::current()->RunAllPending(); // Do async task execution. + EXPECT_EQ(cache_id, delegate.loaded_cache_id_); + EXPECT_EQ(cache_ptr, delegate.loaded_cache_.get()); + delegate.loaded_cache_ = NULL; + + // Conduct the group load test. + EXPECT_NE(manifest_url, delegate.loaded_manifest_url_); + EXPECT_FALSE(delegate.loaded_group_.get()); + storage->LoadOrCreateGroup(manifest_url, &delegate); + EXPECT_NE(manifest_url, delegate.loaded_manifest_url_); + EXPECT_FALSE(delegate.loaded_group_.get()); + MessageLoop::current()->RunAllPending(); // Do async task execution. + EXPECT_EQ(manifest_url, delegate.loaded_manifest_url_); + EXPECT_EQ(group_ptr, delegate.loaded_group_.get()); +} + +TEST_F(MockAppCacheStorageTest, StoreNewGroup) { + // Store a group and its newest cache. Should complete asyncly. + MockAppCacheService service; + MockAppCacheStorage* storage = + reinterpret_cast<MockAppCacheStorage*>(service.storage()); + + // Setup some preconditions. Create a group and newest cache that + // appears to be "unstored". + GURL manifest_url("http://blah/"); + scoped_refptr<AppCacheGroup> group = + new AppCacheGroup(&service, manifest_url); + int64 cache_id = storage->NewCacheId(); + scoped_refptr<AppCache> cache = new AppCache(&service, cache_id); + cache->set_complete(true); + group->AddCache(cache); + // 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. + MockStorageDelegate delegate; + EXPECT_TRUE(storage->stored_caches_.empty()); + EXPECT_TRUE(storage->stored_groups_.empty()); + storage->StoreGroupAndNewestCache(group, &delegate); + EXPECT_FALSE(delegate.stored_group_success_); + EXPECT_TRUE(storage->stored_caches_.empty()); + EXPECT_TRUE(storage->stored_groups_.empty()); + MessageLoop::current()->RunAllPending(); // Do async task execution. + EXPECT_TRUE(delegate.stored_group_success_); + EXPECT_FALSE(storage->stored_caches_.empty()); + EXPECT_FALSE(storage->stored_groups_.empty()); +} + +TEST_F(MockAppCacheStorageTest, StoreExistingGroup) { + // Store a group and its newest cache. Should complete asyncly. + MockAppCacheService service; + MockAppCacheStorage* storage = + reinterpret_cast<MockAppCacheStorage*>(service.storage()); + + // Setup some preconditions. Create a group and old complete cache + // that appear to be "stored", and a newest unstored complete cache. + GURL manifest_url("http://blah/"); + scoped_refptr<AppCacheGroup> group = + new AppCacheGroup(&service, manifest_url); + int64 old_cache_id = storage->NewCacheId(); + scoped_refptr<AppCache> old_cache = new AppCache(&service, old_cache_id); + old_cache->set_complete(true); + group->AddCache(old_cache); + storage->AddStoredGroup(group); + storage->AddStoredCache(old_cache); + int64 new_cache_id = storage->NewCacheId(); + scoped_refptr<AppCache> new_cache = new AppCache(&service, new_cache_id); + new_cache->set_complete(true); + group->AddCache(new_cache); + EXPECT_EQ(new_cache.get(), group->newest_complete_cache()); + // Hold our refs to simulate the UpdateJob holding these refs. + + // Conduct the test. + MockStorageDelegate delegate; + EXPECT_EQ(size_t(1), storage->stored_caches_.size()); + EXPECT_EQ(size_t(1), storage->stored_groups_.size()); + EXPECT_TRUE(storage->IsCacheStored(old_cache)); + EXPECT_FALSE(storage->IsCacheStored(new_cache)); + storage->StoreGroupAndNewestCache(group, &delegate); + EXPECT_FALSE(delegate.stored_group_success_); + EXPECT_EQ(size_t(1), storage->stored_caches_.size()); + EXPECT_EQ(size_t(1), storage->stored_groups_.size()); + EXPECT_TRUE(storage->IsCacheStored(old_cache)); + EXPECT_FALSE(storage->IsCacheStored(new_cache)); + MessageLoop::current()->RunAllPending(); // Do async task execution. + EXPECT_TRUE(delegate.stored_group_success_); + EXPECT_EQ(size_t(1), storage->stored_caches_.size()); + EXPECT_EQ(size_t(1), storage->stored_groups_.size()); + EXPECT_FALSE(storage->IsCacheStored(old_cache)); + EXPECT_TRUE(storage->IsCacheStored(new_cache)); +} + +TEST_F(MockAppCacheStorageTest, MakeGroupObsolete) { + // Make a group obsolete, should complete asyncly. + MockAppCacheService service; + MockAppCacheStorage* storage = + reinterpret_cast<MockAppCacheStorage*>(service.storage()); + + // Setup some preconditions. Create a group and newest cache that + // appears to be "stored" and "currently in use". + GURL manifest_url("http://blah/"); + scoped_refptr<AppCacheGroup> group = + new AppCacheGroup(&service, manifest_url); + int64 cache_id = storage->NewCacheId(); + scoped_refptr<AppCache> cache = new AppCache(&service, cache_id); + cache->set_complete(true); + group->AddCache(cache); + storage->AddStoredGroup(group); + storage->AddStoredCache(cache); + // Hold our refs to simulate the UpdateJob holding these refs. + + // Conduct the test. + MockStorageDelegate delegate; + EXPECT_FALSE(group->is_obsolete()); + EXPECT_EQ(size_t(1), storage->stored_caches_.size()); + EXPECT_EQ(size_t(1), storage->stored_groups_.size()); + EXPECT_FALSE(cache->HasOneRef()); + EXPECT_FALSE(group->HasOneRef()); + storage->MakeGroupObsolete(group, &delegate); + EXPECT_FALSE(group->is_obsolete()); + EXPECT_EQ(size_t(1), storage->stored_caches_.size()); + EXPECT_EQ(size_t(1), storage->stored_groups_.size()); + EXPECT_FALSE(cache->HasOneRef()); + EXPECT_FALSE(group->HasOneRef()); + MessageLoop::current()->RunAllPending(); // Do async task execution. + EXPECT_TRUE(delegate.obsoleted_success_); + EXPECT_EQ(group.get(), delegate.obsoleted_group_.get()); + EXPECT_TRUE(group->is_obsolete()); + EXPECT_TRUE(storage->stored_caches_.empty()); + EXPECT_TRUE(storage->stored_groups_.empty()); + EXPECT_TRUE(cache->HasOneRef()); + EXPECT_FALSE(group->HasOneRef()); + delegate.obsoleted_group_ = NULL; + cache = NULL; + EXPECT_TRUE(group->HasOneRef()); +} + +TEST_F(MockAppCacheStorageTest, MarkEntryAsForeign) { + // Should complete syncly. + MockAppCacheService service; + MockAppCacheStorage* storage = + reinterpret_cast<MockAppCacheStorage*>(service.storage()); + + // Setup some preconditions. Create a cache with an entry. + GURL entry_url("http://blan/entry"); + int64 cache_id = storage->NewCacheId(); + scoped_refptr<AppCache> cache = new AppCache(&service, cache_id); + cache->AddEntry(entry_url, AppCacheEntry(AppCacheEntry::EXPLICIT)); + + // Conduct the test. + MockStorageDelegate delegate; + EXPECT_FALSE(cache->GetEntry(entry_url)->IsForeign()); + storage->MarkEntryAsForeign(entry_url, cache_id); + EXPECT_TRUE(cache->GetEntry(entry_url)->IsForeign()); + EXPECT_TRUE(cache->GetEntry(entry_url)->IsExplicit()); +} + +TEST_F(MockAppCacheStorageTest, FindNoMainResponse) { + // Should complete asyncly. + MockAppCacheService service; + MockAppCacheStorage* storage = + reinterpret_cast<MockAppCacheStorage*>(service.storage()); + + // Conduct the test. + MockStorageDelegate delegate; + GURL url("http://blah/some_url"); + EXPECT_NE(url, delegate.found_url_); + storage->FindResponseForMainRequest(url, &delegate); + EXPECT_NE(url, delegate.found_url_); + MessageLoop::current()->RunAllPending(); // Do async task execution. + EXPECT_EQ(url, delegate.found_url_); + EXPECT_TRUE(delegate.found_manifest_url_.is_empty()); + EXPECT_EQ(kNoCacheId, delegate.found_cache_id_); + EXPECT_EQ(kNoResponseId, delegate.found_entry_.response_id()); + EXPECT_EQ(0, delegate.found_entry_.types()); +} + +} // namespace appcache + |