// Copyright (c) 2009 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 "base/message_loop.h" #include "base/thread.h" #include "base/waitable_event.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_service.h" #include "webkit/appcache/appcache_storage_impl.h" #include "webkit/tools/test_shell/simple_appcache_system.h" namespace appcache { namespace { const base::TimeTicks kZeroTimeTicks; 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 io_thread; scoped_ptr 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), obsoleted_success_(false), found_cache_id_(kNoCacheId), 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, bool success) { stored_group_ = group; stored_group_success_ = success; 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) { found_url_ = url; found_entry_ = entry; found_fallback_entry_ = fallback_entry; found_cache_id_ = cache_id; found_manifest_url_ = manifest_url; test_->ScheduleNextTask(); } scoped_refptr loaded_cache_; int64 loaded_cache_id_; scoped_refptr loaded_group_; GURL loaded_manifest_url_; scoped_refptr loaded_groups_newest_cache_; scoped_refptr stored_group_; bool stored_group_success_; scoped_refptr obsoleted_group_; bool obsoleted_success_; GURL found_url_; AppCacheEntry found_entry_; AppCacheEntry found_fallback_entry_; int64 found_cache_id_; GURL found_manifest_url_; 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 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(method_factory_(this)) { } template void RunTestOnIOThread(Method method) { test_finished_event_ .reset(new base::WaitableEvent(false, false)); io_thread->message_loop()->PostTask( FROM_HERE, new WrapperTask(this, method)); test_finished_event_->Wait(); } void SetUpTest() { DCHECK(MessageLoop::current() == io_thread->message_loop()); service_.reset(new AppCacheService); service_->Initialize(FilePath()); 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, method_factory_.NewRunnableMethod( &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(method_factory_.NewRunnableMethod( &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 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(method_factory_.NewRunnableMethod( &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(method_factory_.NewRunnableMethod( &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(method_factory_.NewRunnableMethod( &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(method_factory_.NewRunnableMethod( &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()); cache_->set_complete(true); // 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()); // 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(method_factory_.NewRunnableMethod( &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); cache2_->set_complete(true); // 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()); // 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::TimeTicks now = base::TimeTicks::Now(); cache_->AddEntry(kEntryUrl, AppCacheEntry(AppCacheEntry::MASTER, 1, 100)); cache_->set_update_time(now); PushNextTask(method_factory_.NewRunnableMethod( &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::TimeTicks 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 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(); } // MakeGroupObsolete ------------------------------- void MakeGroupObsolete() { // Make a group obsolete, should complete asyncly. PushNextTask(method_factory_.NewRunnableMethod( &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 entry_records; database()->FindEntriesForCache(1, &entry_records); EXPECT_TRUE(entry_records.empty()); std::vector fallback_records; database()->FindFallbackNameSpacesForCache(1, &fallback_records); EXPECT_TRUE(fallback_records.empty()); std::vector 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(method_factory_.NewRunnableMethod( &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(method_factory_.NewRunnableMethod( &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_); 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(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); } void BasicFindMainResponseInWorkingSet() { BasicFindMainResponse(false); } void BasicFindMainResponse(bool drop_from_working_set) { PushNextTask(method_factory_.NewRunnableMethod( &AppCacheStorageImplTest::Verify_BasicFindMainResponse)); // 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(kEntryUrl, delegate()->found_url_); EXPECT_EQ(kManifestUrl, delegate()->found_manifest_url_); 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(); } // BasicFindMainFallbackResponse ------------------------------- void BasicFindMainFallbackResponseInDatabase() { BasicFindMainFallbackResponse(true); } void BasicFindMainFallbackResponseInWorkingSet() { BasicFindMainFallbackResponse(false); } void BasicFindMainFallbackResponse(bool drop_from_working_set) { PushNextTask(method_factory_.NewRunnableMethod( &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 entries; std::vector fallbacks; std::vector 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_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(method_factory_.NewRunnableMethod( &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_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(method_factory_.NewRunnableMethod( &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_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(method_factory_.NewRunnableMethod( &AppCacheStorageImplTest::Verify_NotFound, kOnlineNamespace, true)); storage()->FindResponseForMainRequest(kOnlineNamespace, delegate()); return; } TestFinished(); } // Test case helpers -------------------------------------------------- AppCacheService* service() { return service_.get(); } AppCacheStorageImpl* storage() { return static_cast(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 = kZeroTimeTicks; EXPECT_TRUE(database()->InsertCache(&cache_record)); storage()->origins_with_groups_.insert(manifest_url.GetOrigin()); } } // Data members -------------------------------------------------- ScopedRunnableMethodFactory method_factory_; scoped_ptr test_finished_event_; std::stack task_stack_; scoped_ptr service_; scoped_ptr delegate_; scoped_refptr group_; scoped_refptr cache_; scoped_refptr 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, 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, 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