1/* 2 * Copyright (c) 2013, Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31#include "config.h" 32#include "core/fetch/MemoryCache.h" 33 34#include "core/fetch/MockImageResourceClient.h" 35#include "core/fetch/RawResource.h" 36#include "core/fetch/ResourcePtr.h" 37#include "platform/network/ResourceRequest.h" 38#include "public/platform/Platform.h" 39#include "wtf/OwnPtr.h" 40 41#include <gtest/gtest.h> 42 43namespace blink { 44 45class MemoryCacheTest : public ::testing::Test { 46public: 47 class FakeDecodedResource : public blink::Resource { 48 public: 49 FakeDecodedResource(const ResourceRequest& request, Type type) 50 : Resource(request, type) 51 { 52 } 53 54 virtual void appendData(const char* data, int len) 55 { 56 Resource::appendData(data, len); 57 setDecodedSize(this->size()); 58 } 59 60 protected: 61 virtual void destroyDecodedDataIfPossible() OVERRIDE 62 { 63 setDecodedSize(0); 64 } 65 }; 66 67 class FakeResource : public blink::Resource { 68 public: 69 FakeResource(const ResourceRequest& request, Type type) 70 : Resource(request, type) 71 { 72 } 73 74 void fakeEncodedSize(size_t size) 75 { 76 setEncodedSize(size); 77 } 78 }; 79 80protected: 81 virtual void SetUp() 82 { 83 // Save the global memory cache to restore it upon teardown. 84 m_globalMemoryCache = replaceMemoryCacheForTesting(MemoryCache::create()); 85 } 86 87 virtual void TearDown() 88 { 89 replaceMemoryCacheForTesting(m_globalMemoryCache.release()); 90 } 91 92 OwnPtrWillBePersistent<MemoryCache> m_globalMemoryCache; 93}; 94 95// Verifies that setters and getters for cache capacities work correcty. 96TEST_F(MemoryCacheTest, CapacityAccounting) 97{ 98 const size_t sizeMax = ~static_cast<size_t>(0); 99 const size_t totalCapacity = sizeMax / 4; 100 const size_t minDeadCapacity = sizeMax / 16; 101 const size_t maxDeadCapacity = sizeMax / 8; 102 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity); 103 ASSERT_EQ(totalCapacity, memoryCache()->capacity()); 104 ASSERT_EQ(minDeadCapacity, memoryCache()->minDeadCapacity()); 105 ASSERT_EQ(maxDeadCapacity, memoryCache()->maxDeadCapacity()); 106} 107 108TEST_F(MemoryCacheTest, VeryLargeResourceAccounting) 109{ 110 const size_t sizeMax = ~static_cast<size_t>(0); 111 const size_t totalCapacity = sizeMax / 4; 112 const size_t minDeadCapacity = sizeMax / 16; 113 const size_t maxDeadCapacity = sizeMax / 8; 114 const size_t resourceSize1 = sizeMax / 16; 115 const size_t resourceSize2 = sizeMax / 20; 116 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity); 117 ResourcePtr<FakeResource> cachedResource = 118 new FakeResource(ResourceRequest("http://test/resource"), Resource::Raw); 119 cachedResource->fakeEncodedSize(resourceSize1); 120 121 ASSERT_EQ(0u, memoryCache()->deadSize()); 122 ASSERT_EQ(0u, memoryCache()->liveSize()); 123 memoryCache()->add(cachedResource.get()); 124 ASSERT_EQ(cachedResource->size(), memoryCache()->deadSize()); 125 ASSERT_EQ(0u, memoryCache()->liveSize()); 126 127 MockImageResourceClient client; 128 cachedResource->addClient(&client); 129 ASSERT_EQ(0u, memoryCache()->deadSize()); 130 ASSERT_EQ(cachedResource->size(), memoryCache()->liveSize()); 131 132 cachedResource->fakeEncodedSize(resourceSize2); 133 ASSERT_EQ(0u, memoryCache()->deadSize()); 134 ASSERT_EQ(cachedResource->size(), memoryCache()->liveSize()); 135 136 cachedResource->removeClient(&client); 137} 138 139// Verifies that dead resources that exceed dead resource capacity are evicted 140// from cache when pruning. 141TEST_F(MemoryCacheTest, DeadResourceEviction) 142{ 143 memoryCache()->setDelayBeforeLiveDecodedPrune(0); 144 memoryCache()->setMaxPruneDeferralDelay(0); 145 const unsigned totalCapacity = 1000000; 146 const unsigned minDeadCapacity = 0; 147 const unsigned maxDeadCapacity = 0; 148 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity); 149 150 Resource* cachedResource = 151 new Resource(ResourceRequest("http://test/resource"), Resource::Raw); 152 const char data[5] = "abcd"; 153 cachedResource->appendData(data, 3); 154 // The resource size has to be nonzero for this test to be meaningful, but 155 // we do not rely on it having any particular value. 156 ASSERT_GT(cachedResource->size(), 0u); 157 158 ASSERT_EQ(0u, memoryCache()->deadSize()); 159 ASSERT_EQ(0u, memoryCache()->liveSize()); 160 161 memoryCache()->add(cachedResource); 162 ASSERT_EQ(cachedResource->size(), memoryCache()->deadSize()); 163 ASSERT_EQ(0u, memoryCache()->liveSize()); 164 165 memoryCache()->prune(); 166 ASSERT_EQ(0u, memoryCache()->deadSize()); 167 ASSERT_EQ(0u, memoryCache()->liveSize()); 168} 169 170// Verified that when ordering a prune in a runLoop task, the prune 171// is deferred to the end of the task. 172TEST_F(MemoryCacheTest, LiveResourceEvictionAtEndOfTask) 173{ 174 memoryCache()->setDelayBeforeLiveDecodedPrune(0); 175 const unsigned totalCapacity = 1; 176 const unsigned minDeadCapacity = 0; 177 const unsigned maxDeadCapacity = 0; 178 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity); 179 const char data[6] = "abcde"; 180 Resource* cachedDeadResource = 181 new Resource(ResourceRequest("hhtp://foo"), Resource::Raw); 182 cachedDeadResource->appendData(data, 3); 183 ResourcePtr<Resource> cachedLiveResource = 184 new FakeDecodedResource(ResourceRequest("http://test/resource"), Resource::Raw); 185 MockImageResourceClient client; 186 cachedLiveResource->addClient(&client); 187 cachedLiveResource->appendData(data, 4); 188 189 class Task1 : public blink::WebThread::Task { 190 public: 191 Task1(const ResourcePtr<Resource>& live, Resource* dead) 192 : m_live(live) 193 , m_dead(dead) 194 { } 195 196 virtual void run() OVERRIDE 197 { 198 // The resource size has to be nonzero for this test to be meaningful, but 199 // we do not rely on it having any particular value. 200 ASSERT_GT(m_live->size(), 0u); 201 ASSERT_GT(m_dead->size(), 0u); 202 203 ASSERT_EQ(0u, memoryCache()->deadSize()); 204 ASSERT_EQ(0u, memoryCache()->liveSize()); 205 206 memoryCache()->add(m_dead); 207 memoryCache()->add(m_live.get()); 208 memoryCache()->updateDecodedResource(m_live.get(), UpdateForPropertyChange); 209 ASSERT_EQ(m_dead->size(), memoryCache()->deadSize()); 210 ASSERT_EQ(m_live->size(), memoryCache()->liveSize()); 211 ASSERT_GT(m_live->decodedSize(), 0u); 212 213 memoryCache()->prune(); // Dead resources are pruned immediately 214 ASSERT_EQ(m_dead->size(), memoryCache()->deadSize()); 215 ASSERT_EQ(m_live->size(), memoryCache()->liveSize()); 216 ASSERT_GT(m_live->decodedSize(), 0u); 217 } 218 219 private: 220 ResourcePtr<Resource> m_live; 221 Resource* m_dead; 222 }; 223 224 class Task2 : public blink::WebThread::Task { 225 public: 226 Task2(unsigned liveSizeWithoutDecode) 227 : m_liveSizeWithoutDecode(liveSizeWithoutDecode) { } 228 229 virtual void run() OVERRIDE 230 { 231 // Next task: now, the live resource was evicted. 232 ASSERT_EQ(0u, memoryCache()->deadSize()); 233 ASSERT_EQ(m_liveSizeWithoutDecode, memoryCache()->liveSize()); 234 blink::Platform::current()->currentThread()->exitRunLoop(); 235 } 236 237 private: 238 unsigned m_liveSizeWithoutDecode; 239 }; 240 241 242 blink::Platform::current()->currentThread()->postTask(new Task1(cachedLiveResource, cachedDeadResource)); 243 blink::Platform::current()->currentThread()->postTask(new Task2(cachedLiveResource->encodedSize() + cachedLiveResource->overheadSize())); 244 blink::Platform::current()->currentThread()->enterRunLoop(); 245 cachedLiveResource->removeClient(&client); 246} 247 248// Verifies that cached resources are evicted immediately after release when 249// the total dead resource size is more than double the dead resource capacity. 250TEST_F(MemoryCacheTest, ClientRemoval) 251{ 252 const char data[6] = "abcde"; 253 ResourcePtr<Resource> resource1 = 254 new FakeDecodedResource(ResourceRequest("http://foo.com"), Resource::Raw); 255 MockImageResourceClient client1; 256 resource1->addClient(&client1); 257 resource1->appendData(data, 4); 258 ResourcePtr<Resource> resource2 = 259 new FakeDecodedResource(ResourceRequest("http://test/resource"), Resource::Raw); 260 MockImageResourceClient client2; 261 resource2->addClient(&client2); 262 resource2->appendData(data, 4); 263 264 const unsigned minDeadCapacity = 0; 265 const unsigned maxDeadCapacity = ((resource1->size() + resource2->size()) / 2) - 1; 266 const unsigned totalCapacity = maxDeadCapacity; 267 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity); 268 memoryCache()->add(resource1.get()); 269 memoryCache()->add(resource2.get()); 270 // Call prune. There is nothing to prune, but this will initialize 271 // the prune timestamp, allowing future prunes to be deferred. 272 memoryCache()->prune(); 273 ASSERT_GT(resource1->decodedSize(), 0u); 274 ASSERT_GT(resource2->decodedSize(), 0u); 275 ASSERT_EQ(memoryCache()->deadSize(), 0u); 276 ASSERT_EQ(memoryCache()->liveSize(), resource1->size() + resource2->size()); 277 278 // Removing the client from resource1 should result in all resources 279 // remaining in cache since the prune is deferred. 280 resource1->removeClient(&client1); 281 ASSERT_GT(resource1->decodedSize(), 0u); 282 ASSERT_GT(resource2->decodedSize(), 0u); 283 ASSERT_EQ(memoryCache()->deadSize(), resource1->size()); 284 ASSERT_EQ(memoryCache()->liveSize(), resource2->size()); 285 ASSERT_TRUE(memoryCache()->contains(resource1.get())); 286 ASSERT_TRUE(memoryCache()->contains(resource2.get())); 287 288 // Removing the client from resource2 should result in immediate 289 // eviction of resource2 because we are over the prune deferral limit. 290 resource2->removeClient(&client2); 291 ASSERT_GT(resource1->decodedSize(), 0u); 292 ASSERT_GT(resource2->decodedSize(), 0u); 293 ASSERT_EQ(memoryCache()->deadSize(), resource1->size()); 294 ASSERT_EQ(memoryCache()->liveSize(), 0u); 295 ASSERT_TRUE(memoryCache()->contains(resource1.get())); 296 ASSERT_FALSE(memoryCache()->contains(resource2.get())); 297} 298 299// Verifies that CachedResources are evicted from the decode cache 300// according to their DecodeCachePriority. 301TEST_F(MemoryCacheTest, DecodeCacheOrder) 302{ 303 memoryCache()->setDelayBeforeLiveDecodedPrune(0); 304 memoryCache()->setMaxPruneDeferralDelay(0); 305 ResourcePtr<FakeDecodedResource> cachedImageLowPriority = 306 new FakeDecodedResource(ResourceRequest("http://foo.com"), Resource::Raw); 307 ResourcePtr<FakeDecodedResource> cachedImageHighPriority = 308 new FakeDecodedResource(ResourceRequest("http://test/resource"), Resource::Raw); 309 310 MockImageResourceClient clientLowPriority; 311 MockImageResourceClient clientHighPriority; 312 cachedImageLowPriority->addClient(&clientLowPriority); 313 cachedImageHighPriority->addClient(&clientHighPriority); 314 315 const char data[5] = "abcd"; 316 cachedImageLowPriority->appendData(data, 1); 317 cachedImageHighPriority->appendData(data, 4); 318 const unsigned lowPrioritySize = cachedImageLowPriority->size(); 319 const unsigned highPrioritySize = cachedImageHighPriority->size(); 320 const unsigned lowPriorityMockDecodeSize = cachedImageLowPriority->decodedSize(); 321 const unsigned highPriorityMockDecodeSize = cachedImageHighPriority->decodedSize(); 322 const unsigned totalSize = lowPrioritySize + highPrioritySize; 323 324 // Verify that the sizes are different to ensure that we can test eviction order. 325 ASSERT_GT(lowPrioritySize, 0u); 326 ASSERT_NE(lowPrioritySize, highPrioritySize); 327 ASSERT_GT(lowPriorityMockDecodeSize, 0u); 328 ASSERT_NE(lowPriorityMockDecodeSize, highPriorityMockDecodeSize); 329 330 ASSERT_EQ(memoryCache()->deadSize(), 0u); 331 ASSERT_EQ(memoryCache()->liveSize(), 0u); 332 333 // Add the items. The item added first would normally be evicted first. 334 memoryCache()->add(cachedImageHighPriority.get()); 335 ASSERT_EQ(memoryCache()->deadSize(), 0u); 336 ASSERT_EQ(memoryCache()->liveSize(), highPrioritySize); 337 338 memoryCache()->add(cachedImageLowPriority.get()); 339 ASSERT_EQ(memoryCache()->deadSize(), 0u); 340 ASSERT_EQ(memoryCache()->liveSize(), highPrioritySize + lowPrioritySize); 341 342 // Insert all items in the decoded items list with the same priority 343 memoryCache()->updateDecodedResource(cachedImageHighPriority.get(), UpdateForPropertyChange); 344 memoryCache()->updateDecodedResource(cachedImageLowPriority.get(), UpdateForPropertyChange); 345 ASSERT_EQ(memoryCache()->deadSize(), 0u); 346 ASSERT_EQ(memoryCache()->liveSize(), totalSize); 347 348 // Now we will assign their priority and make sure they are moved to the correct buckets. 349 memoryCache()->updateDecodedResource(cachedImageLowPriority.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityLow); 350 memoryCache()->updateDecodedResource(cachedImageHighPriority.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityHigh); 351 352 // Should first prune the LowPriority item. 353 memoryCache()->setCapacities(memoryCache()->minDeadCapacity(), memoryCache()->liveSize() - 10, memoryCache()->liveSize() - 10); 354 memoryCache()->prune(); 355 ASSERT_EQ(memoryCache()->deadSize(), 0u); 356 ASSERT_EQ(memoryCache()->liveSize(), totalSize - lowPriorityMockDecodeSize); 357 358 // Should prune the HighPriority item. 359 memoryCache()->setCapacities(memoryCache()->minDeadCapacity(), memoryCache()->liveSize() - 10, memoryCache()->liveSize() - 10); 360 memoryCache()->prune(); 361 ASSERT_EQ(memoryCache()->deadSize(), 0u); 362 ASSERT_EQ(memoryCache()->liveSize(), totalSize - lowPriorityMockDecodeSize - highPriorityMockDecodeSize); 363 364 cachedImageLowPriority->removeClient(&clientLowPriority); 365 cachedImageHighPriority->removeClient(&clientHighPriority); 366} 367 368TEST_F(MemoryCacheTest, MultipleReplace) 369{ 370 ResourcePtr<FakeResource> resource1 = new FakeResource(ResourceRequest("http://test/resource"), Resource::Raw); 371 memoryCache()->add(resource1.get()); 372 373 ResourcePtr<FakeResource> resource2 = new FakeResource(ResourceRequest("http://test/resource"), Resource::Raw); 374 memoryCache()->replace(resource2.get(), resource1.get()); 375 EXPECT_TRUE(memoryCache()->contains(resource2.get())); 376 EXPECT_FALSE(memoryCache()->contains(resource1.get())); 377 378 ResourcePtr<FakeResource> resource3 = new FakeResource(ResourceRequest("http://test/resource"), Resource::Raw); 379 memoryCache()->replace(resource3.get(), resource2.get()); 380 EXPECT_TRUE(memoryCache()->contains(resource3.get())); 381 EXPECT_FALSE(memoryCache()->contains(resource2.get())); 382} 383 384TEST_F(MemoryCacheTest, RemoveDuringRevalidation) 385{ 386 ResourcePtr<FakeResource> resource1 = new FakeResource(ResourceRequest("http://test/resource"), Resource::Raw); 387 memoryCache()->add(resource1.get()); 388 389 ResourcePtr<FakeResource> resource2 = new FakeResource(ResourceRequest("http://test/resource"), Resource::Raw); 390 memoryCache()->remove(resource1.get()); 391 memoryCache()->add(resource2.get()); 392 EXPECT_TRUE(memoryCache()->contains(resource2.get())); 393 EXPECT_FALSE(memoryCache()->contains(resource1.get())); 394 395 ResourcePtr<FakeResource> resource3 = new FakeResource(ResourceRequest("http://test/resource"), Resource::Raw); 396 memoryCache()->remove(resource2.get()); 397 memoryCache()->add(resource3.get()); 398 EXPECT_TRUE(memoryCache()->contains(resource3.get())); 399 EXPECT_FALSE(memoryCache()->contains(resource2.get())); 400 401 memoryCache()->replace(resource1.get(), resource2.get()); 402 EXPECT_TRUE(memoryCache()->contains(resource1.get())); 403 EXPECT_FALSE(memoryCache()->contains(resource2.get())); 404 EXPECT_FALSE(memoryCache()->contains(resource3.get())); 405} 406 407} // namespace 408