|  | // Copyright (c) 2011 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 <stddef.h> | 
|  |  | 
|  | #include <string> | 
|  |  | 
|  | #include "base/message_loop/message_loop.h" | 
|  | #include "components/web_cache/browser/web_cache_manager.h" | 
|  | #include "content/public/test/test_browser_thread.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | using base::Time; | 
|  | using base::TimeDelta; | 
|  | using content::BrowserThread; | 
|  |  | 
|  | namespace web_cache { | 
|  |  | 
|  | class WebCacheManagerTest : public testing::Test { | 
|  | protected: | 
|  | typedef WebCacheManager::StatsMap StatsMap; | 
|  | typedef WebCacheManager::Allocation Allocation; | 
|  | typedef WebCacheManager::AllocationStrategy AllocationStrategy; | 
|  |  | 
|  | static const int kRendererID; | 
|  | static const int kRendererID2; | 
|  | static const WebCacheManager::RendererInfo kStats; | 
|  | static const WebCacheManager::RendererInfo kStats2; | 
|  |  | 
|  | WebCacheManagerTest() | 
|  | : ui_thread_(BrowserThread::UI, &message_loop_) { | 
|  | } | 
|  |  | 
|  | // Thunks to access protected members of WebCacheManager | 
|  | static std::map<int, WebCacheManager::RendererInfo>& stats( | 
|  | WebCacheManager* h) { | 
|  | return h->stats_; | 
|  | } | 
|  |  | 
|  | static void SimulateInactivity(WebCacheManager* h, int renderer_id) { | 
|  | stats(h)[renderer_id].access = Time::Now() - TimeDelta::FromMinutes( | 
|  | WebCacheManager::kRendererInactiveThresholdMinutes); | 
|  | h->FindInactiveRenderers(); | 
|  | } | 
|  |  | 
|  | static std::set<int>& active_renderers(WebCacheManager* h) { | 
|  | return h->active_renderers_; | 
|  | } | 
|  | static std::set<int>& inactive_renderers(WebCacheManager* h) { | 
|  | return h->inactive_renderers_; | 
|  | } | 
|  | static void GatherStats(WebCacheManager* h, | 
|  | std::set<int> renderers, | 
|  | WebCacheManager::RendererInfo* stats) { | 
|  | memset(stats, 0, sizeof(WebCacheManager::RendererInfo)); | 
|  | h->GatherStats(renderers, &stats->capacity, &stats->size); | 
|  | } | 
|  | static uint64_t GetSize(int tactic, | 
|  | const WebCacheManager::RendererInfo& stats) { | 
|  | return WebCacheManager::GetSize( | 
|  | static_cast<WebCacheManager::AllocationTactic>(tactic), stats.size); | 
|  | } | 
|  | static bool AttemptTactic(WebCacheManager* h, | 
|  | int active_tactic, | 
|  | const WebCacheManager::RendererInfo& active_stats, | 
|  | int inactive_tactic, | 
|  | const WebCacheManager::RendererInfo& inactive_stats, | 
|  | std::list<std::pair<int, uint64_t>>* strategy) { | 
|  | return h->AttemptTactic( | 
|  | static_cast<WebCacheManager::AllocationTactic>(active_tactic), | 
|  | active_stats.size, | 
|  | static_cast<WebCacheManager::AllocationTactic>(inactive_tactic), | 
|  | inactive_stats.size, strategy); | 
|  | } | 
|  | static void AddToStrategy(WebCacheManager* h, | 
|  | std::set<int> renderers, | 
|  | int tactic, | 
|  | uint64_t extra_bytes_to_allocate, | 
|  | std::list<std::pair<int, uint64_t>>* strategy) { | 
|  | h->AddToStrategy(renderers, | 
|  | static_cast<WebCacheManager::AllocationTactic>(tactic), | 
|  | extra_bytes_to_allocate, | 
|  | strategy); | 
|  | } | 
|  |  | 
|  | static bool RendererInfoEqual(const WebCacheManager::RendererInfo& lhs, | 
|  | const WebCacheManager::RendererInfo& rhs) { | 
|  | return lhs.capacity == rhs.capacity && lhs.size == rhs.size; | 
|  | } | 
|  |  | 
|  | enum { | 
|  | DIVIDE_EVENLY = WebCacheManager::DIVIDE_EVENLY, | 
|  | KEEP_CURRENT_WITH_HEADROOM = WebCacheManager::KEEP_CURRENT_WITH_HEADROOM, | 
|  | KEEP_CURRENT = WebCacheManager::KEEP_CURRENT, | 
|  | }; | 
|  |  | 
|  | WebCacheManager* manager() { return &manager_; } | 
|  |  | 
|  | private: | 
|  | WebCacheManager manager_; | 
|  | base::MessageLoop message_loop_; | 
|  | content::TestBrowserThread ui_thread_; | 
|  | }; | 
|  |  | 
|  | // static | 
|  | const int WebCacheManagerTest::kRendererID = 146; | 
|  |  | 
|  | // static | 
|  | const int WebCacheManagerTest::kRendererID2 = 245; | 
|  |  | 
|  | // static | 
|  | const WebCacheManager::RendererInfo WebCacheManagerTest::kStats = { | 
|  | base::Time(), 1024 * 1024, 256 * 1024 + 512, | 
|  | }; | 
|  |  | 
|  | // static | 
|  | const WebCacheManager::RendererInfo WebCacheManagerTest::kStats2 = { | 
|  | base::Time(), 2 * 1024 * 1024, 2 * 256 * 1024 + 2 * 512, | 
|  | }; | 
|  |  | 
|  | TEST_F(WebCacheManagerTest, AddRemoveRendererTest) { | 
|  | EXPECT_EQ(0U, active_renderers(manager()).size()); | 
|  | EXPECT_EQ(0U, inactive_renderers(manager()).size()); | 
|  |  | 
|  | manager()->Add(kRendererID); | 
|  | EXPECT_EQ(1U, active_renderers(manager()).count(kRendererID)); | 
|  | EXPECT_EQ(0U, inactive_renderers(manager()).count(kRendererID)); | 
|  |  | 
|  | manager()->Remove(kRendererID); | 
|  | EXPECT_EQ(0U, active_renderers(manager()).size()); | 
|  | EXPECT_EQ(0U, inactive_renderers(manager()).size()); | 
|  | } | 
|  |  | 
|  | TEST_F(WebCacheManagerTest, ActiveInactiveTest) { | 
|  | manager()->Add(kRendererID); | 
|  |  | 
|  | manager()->ObserveActivity(kRendererID); | 
|  | EXPECT_EQ(1U, active_renderers(manager()).count(kRendererID)); | 
|  | EXPECT_EQ(0U, inactive_renderers(manager()).count(kRendererID)); | 
|  |  | 
|  | SimulateInactivity(manager(), kRendererID); | 
|  | EXPECT_EQ(0U, active_renderers(manager()).count(kRendererID)); | 
|  | EXPECT_EQ(1U, inactive_renderers(manager()).count(kRendererID)); | 
|  |  | 
|  | manager()->ObserveActivity(kRendererID); | 
|  | EXPECT_EQ(1U, active_renderers(manager()).count(kRendererID)); | 
|  | EXPECT_EQ(0U, inactive_renderers(manager()).count(kRendererID)); | 
|  |  | 
|  | manager()->Remove(kRendererID); | 
|  | } | 
|  |  | 
|  | TEST_F(WebCacheManagerTest, ObserveStatsTest) { | 
|  | manager()->Add(kRendererID); | 
|  |  | 
|  | EXPECT_EQ(1U, stats(manager()).size()); | 
|  |  | 
|  | manager()->ObserveStats(kRendererID, kStats.capacity, kStats.size); | 
|  |  | 
|  | EXPECT_EQ(1U, stats(manager()).size()); | 
|  | EXPECT_TRUE(RendererInfoEqual(kStats, stats(manager())[kRendererID])); | 
|  |  | 
|  | manager()->Remove(kRendererID); | 
|  | } | 
|  |  | 
|  | TEST_F(WebCacheManagerTest, SetGlobalSizeLimitTest) { | 
|  | uint64_t limit = manager()->GetDefaultGlobalSizeLimit(); | 
|  | manager()->SetGlobalSizeLimit(limit); | 
|  | EXPECT_EQ(limit, manager()->global_size_limit()); | 
|  |  | 
|  | manager()->SetGlobalSizeLimit(0); | 
|  | EXPECT_EQ(0U, manager()->global_size_limit()); | 
|  | } | 
|  |  | 
|  | TEST_F(WebCacheManagerTest, GatherStatsTest) { | 
|  | manager()->Add(kRendererID); | 
|  | manager()->Add(kRendererID2); | 
|  |  | 
|  | manager()->ObserveStats(kRendererID, kStats.capacity, kStats.size); | 
|  | manager()->ObserveStats(kRendererID2, kStats2.capacity, kStats2.size); | 
|  |  | 
|  | std::set<int> renderer_set; | 
|  | renderer_set.insert(kRendererID); | 
|  |  | 
|  | WebCacheManager::RendererInfo stats; | 
|  | GatherStats(manager(), renderer_set, &stats); | 
|  |  | 
|  | EXPECT_TRUE(RendererInfoEqual(kStats, stats)); | 
|  |  | 
|  | renderer_set.insert(kRendererID2); | 
|  | GatherStats(manager(), renderer_set, &stats); | 
|  |  | 
|  | WebCacheManager::RendererInfo expected_stats = kStats; | 
|  | expected_stats.capacity += kStats2.capacity; | 
|  | expected_stats.size += kStats2.size; | 
|  |  | 
|  | EXPECT_TRUE(RendererInfoEqual(expected_stats, stats)); | 
|  |  | 
|  | manager()->Remove(kRendererID); | 
|  | manager()->Remove(kRendererID2); | 
|  | } | 
|  |  | 
|  | TEST_F(WebCacheManagerTest, GetSizeTest) { | 
|  | EXPECT_EQ(0U, GetSize(DIVIDE_EVENLY, kStats)); | 
|  | EXPECT_LT(256 * 1024u + 512, GetSize(KEEP_CURRENT_WITH_HEADROOM, kStats)); | 
|  | EXPECT_EQ(256 * 1024u + 512, GetSize(KEEP_CURRENT, kStats)); | 
|  | } | 
|  |  | 
|  | TEST_F(WebCacheManagerTest, AttemptTacticTest) { | 
|  | manager()->Add(kRendererID); | 
|  | manager()->Add(kRendererID2); | 
|  |  | 
|  | manager()->ObserveActivity(kRendererID); | 
|  | SimulateInactivity(manager(), kRendererID2); | 
|  |  | 
|  | manager()->ObserveStats(kRendererID, kStats.capacity, kStats.size); | 
|  | manager()->ObserveStats(kRendererID2, kStats2.capacity, kStats2.size); | 
|  |  | 
|  | AllocationStrategy strategy; | 
|  |  | 
|  | manager()->SetGlobalSizeLimit(kStats.size + kStats2.size - 1); | 
|  | EXPECT_FALSE(AttemptTactic(manager(), | 
|  | KEEP_CURRENT, | 
|  | kStats, | 
|  | KEEP_CURRENT, | 
|  | kStats2, | 
|  | &strategy)); | 
|  | EXPECT_TRUE(strategy.empty()); | 
|  |  | 
|  | manager()->SetGlobalSizeLimit(kStats.size + kStats2.size); | 
|  | EXPECT_TRUE(AttemptTactic(manager(), KEEP_CURRENT, kStats, KEEP_CURRENT, | 
|  | kStats2, &strategy)); | 
|  | EXPECT_EQ(2U, strategy.size()); | 
|  |  | 
|  | AllocationStrategy::iterator iter = strategy.begin(); | 
|  | while (iter != strategy.end()) { | 
|  | if (iter->first == kRendererID) | 
|  | EXPECT_LE(kStats.size, iter->second); | 
|  | else if (iter->first == kRendererID2) | 
|  | EXPECT_LE(kStats2.size, iter->second); | 
|  | else | 
|  | ADD_FAILURE();   // Unexpected entry in strategy. | 
|  | ++iter; | 
|  | } | 
|  |  | 
|  | manager()->Remove(kRendererID); | 
|  | manager()->Remove(kRendererID2); | 
|  | } | 
|  |  | 
|  | TEST_F(WebCacheManagerTest, AddToStrategyTest) { | 
|  | manager()->Add(kRendererID); | 
|  | manager()->Add(kRendererID2); | 
|  |  | 
|  | std::set<int> renderer_set; | 
|  | renderer_set.insert(kRendererID); | 
|  | renderer_set.insert(kRendererID2); | 
|  |  | 
|  | manager()->ObserveStats(kRendererID, kStats.capacity, kStats.size); | 
|  | manager()->ObserveStats(kRendererID2, kStats2.capacity, kStats2.size); | 
|  |  | 
|  | const uint64_t kExtraBytesToAllocate = 10 * 1024; | 
|  |  | 
|  | AllocationStrategy strategy; | 
|  | AddToStrategy(manager(), | 
|  | renderer_set, | 
|  | KEEP_CURRENT, | 
|  | kExtraBytesToAllocate, | 
|  | &strategy); | 
|  |  | 
|  | EXPECT_EQ(2U, strategy.size()); | 
|  |  | 
|  | uint64_t total_bytes = 0; | 
|  | AllocationStrategy::iterator iter = strategy.begin(); | 
|  | while (iter != strategy.end()) { | 
|  | total_bytes += iter->second; | 
|  |  | 
|  | if (iter->first == kRendererID) | 
|  | EXPECT_LE(kStats.size, iter->second); | 
|  | else if (iter->first == kRendererID2) | 
|  | EXPECT_LE(kStats2.size, iter->second); | 
|  | else | 
|  | ADD_FAILURE();  // Unexpected entry in strategy. | 
|  | ++iter; | 
|  | } | 
|  |  | 
|  | uint64_t expected_total_bytes = | 
|  | kExtraBytesToAllocate + kStats.size + kStats2.size; | 
|  |  | 
|  | EXPECT_GE(expected_total_bytes, total_bytes); | 
|  |  | 
|  | manager()->Remove(kRendererID); | 
|  | manager()->Remove(kRendererID2); | 
|  | } | 
|  |  | 
|  | // Regression test for http://crbug.com/12362. | 
|  | // There are three operations in the following order will cause the crash: | 
|  | // Remove(kRendererID) -> ObserveActivity(kRendererID) -> Remove(kRendererID2) | 
|  | // To prevent similar failures in the future, 6 tests are added in total to | 
|  | // cover all the possible orderings of these three operations. | 
|  | TEST_F(WebCacheManagerTest, | 
|  | CallRemoveRendererAndObserveActivityInAnyOrderShouldNotCrashTest_1) { | 
|  | EXPECT_EQ(0U, active_renderers(manager()).size()); | 
|  | EXPECT_EQ(0U, inactive_renderers(manager()).size()); | 
|  |  | 
|  | // Add, Remove, and ObserveActivity trigger deferred | 
|  | // calls to ReviseAllocationStrategy and that we call it directly after each | 
|  | // operation to sidestep the need to wait for an unobservable background | 
|  | // operation. | 
|  | manager()->Add(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  | manager()->Add(kRendererID2); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | // The following order will cause a crash in http://crbug.com/12362. | 
|  | manager()->Remove(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->ObserveActivity(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->Remove(kRendererID2); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  | } | 
|  |  | 
|  | TEST_F(WebCacheManagerTest, | 
|  | CallRemoveRendererAndObserveActivityInAnyOrderShouldNotCrashTest_2) { | 
|  | EXPECT_EQ(0U, active_renderers(manager()).size()); | 
|  | EXPECT_EQ(0U, inactive_renderers(manager()).size()); | 
|  |  | 
|  | // Add, Remove, and ObserveActivity trigger deferred | 
|  | // calls to ReviseAllocationStrategy and that we call it directly after each | 
|  | // operation to sidestep the need to wait for an unobservable background | 
|  | // operation. | 
|  | manager()->Add(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  | manager()->Add(kRendererID2); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->Remove(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->Remove(kRendererID2); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->ObserveActivity(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | } | 
|  |  | 
|  | TEST_F(WebCacheManagerTest, | 
|  | CallRemoveRendererAndObserveActivityInAnyOrderShouldNotCrashTest_3) { | 
|  | EXPECT_EQ(0U, active_renderers(manager()).size()); | 
|  | EXPECT_EQ(0U, inactive_renderers(manager()).size()); | 
|  |  | 
|  | // Add, Remove, and ObserveActivity trigger deferred | 
|  | // calls to ReviseAllocationStrategy and that we call it directly after each | 
|  | // operation to sidestep the need to wait for an unobservable background | 
|  | // operation. | 
|  | manager()->Add(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  | manager()->Add(kRendererID2); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->ObserveActivity(kRendererID); | 
|  | EXPECT_EQ(0U, inactive_renderers(manager()).size()); | 
|  |  | 
|  | manager()->Remove(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->Remove(kRendererID2); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  | } | 
|  |  | 
|  | TEST_F(WebCacheManagerTest, | 
|  | CallRemoveRendererAndObserveActivityInAnyOrderShouldNotCrashTest_4) { | 
|  | EXPECT_EQ(0U, active_renderers(manager()).size()); | 
|  | EXPECT_EQ(0U, inactive_renderers(manager()).size()); | 
|  |  | 
|  | // Add, Remove, and ObserveActivity trigger deferred | 
|  | // calls to ReviseAllocationStrategy and that we call it directly after each | 
|  | // operation to sidestep the need to wait for an unobservable background | 
|  | // operation. | 
|  | manager()->Add(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  | manager()->Add(kRendererID2); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->ObserveActivity(kRendererID); | 
|  | EXPECT_EQ(0U, inactive_renderers(manager()).size()); | 
|  |  | 
|  | manager()->Remove(kRendererID2); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->Remove(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  | } | 
|  |  | 
|  | TEST_F(WebCacheManagerTest, | 
|  | CallRemoveRendererAndObserveActivityInAnyOrderShouldNotCrashTest_5) { | 
|  | EXPECT_EQ(0U, active_renderers(manager()).size()); | 
|  | EXPECT_EQ(0U, inactive_renderers(manager()).size()); | 
|  |  | 
|  | // Add, Remove, and ObserveActivity trigger deferred | 
|  | // calls to ReviseAllocationStrategy and that we call it directly after each | 
|  | // operation to sidestep the need to wait for an unobservable background | 
|  | // operation. | 
|  | manager()->Add(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  | manager()->Add(kRendererID2); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->Remove(kRendererID2); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->ObserveActivity(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->Remove(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  | } | 
|  |  | 
|  | TEST_F(WebCacheManagerTest, | 
|  | CallRemoveRendererAndObserveActivityInAnyOrderShouldNotCrashTest_6) { | 
|  | EXPECT_EQ(0U, active_renderers(manager()).size()); | 
|  | EXPECT_EQ(0U, inactive_renderers(manager()).size()); | 
|  |  | 
|  | // Add, Remove, and ObserveActivity trigger deferred | 
|  | // calls to ReviseAllocationStrategy and that we call it directly after each | 
|  | // operation to sidestep the need to wait for an unobservable background | 
|  | // operation. | 
|  | manager()->Add(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  | manager()->Add(kRendererID2); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->Remove(kRendererID2); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->Remove(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  |  | 
|  | manager()->ObserveActivity(kRendererID); | 
|  | manager()->ReviseAllocationStrategy(); | 
|  | } | 
|  |  | 
|  | }  // namespace web_cache |