2 * Copyright (c) 2013, Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
32 #include "core/fetch/MemoryCache.h"
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"
41 #include <gtest/gtest.h>
45 class MemoryCacheTest : public ::testing::Test {
47 class FakeDecodedResource : public WebCore::Resource {
49 FakeDecodedResource(const ResourceRequest& request, Type type)
50 : Resource(request, type)
54 virtual void appendData(const char* data, int len)
56 Resource::appendData(data, len);
57 setDecodedSize(this->size());
61 virtual void destroyDecodedDataIfPossible() OVERRIDE
67 class FakeResource : public WebCore::Resource {
69 FakeResource(const ResourceRequest& request, Type type)
70 : Resource(request, type)
74 void fakeEncodedSize(size_t size)
83 // Save the global memory cache to restore it upon teardown.
84 m_globalMemoryCache = adoptPtr(memoryCache());
85 // Create the test memory cache instance and hook it in.
86 m_testingMemoryCache = adoptPtr(new MemoryCache());
87 setMemoryCacheForTesting(m_testingMemoryCache.leakPtr());
90 virtual void TearDown()
92 // Regain the ownership of testing memory cache, so that it will be
94 m_testingMemoryCache = adoptPtr(memoryCache());
95 // Yield the ownership of the global memory cache back.
96 setMemoryCacheForTesting(m_globalMemoryCache.leakPtr());
99 OwnPtr<MemoryCache> m_testingMemoryCache;
100 OwnPtr<MemoryCache> m_globalMemoryCache;
103 // Verifies that setters and getters for cache capacities work correcty.
104 TEST_F(MemoryCacheTest, CapacityAccounting)
106 const size_t sizeMax = ~static_cast<size_t>(0);
107 const size_t totalCapacity = sizeMax / 4;
108 const size_t minDeadCapacity = sizeMax / 16;
109 const size_t maxDeadCapacity = sizeMax / 8;
110 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity);
111 ASSERT_EQ(totalCapacity, memoryCache()->capacity());
112 ASSERT_EQ(minDeadCapacity, memoryCache()->minDeadCapacity());
113 ASSERT_EQ(maxDeadCapacity, memoryCache()->maxDeadCapacity());
116 TEST_F(MemoryCacheTest, VeryLargeResourceAccounting)
118 const size_t sizeMax = ~static_cast<size_t>(0);
119 const size_t totalCapacity = sizeMax / 4;
120 const size_t minDeadCapacity = sizeMax / 16;
121 const size_t maxDeadCapacity = sizeMax / 8;
122 const size_t resourceSize1 = sizeMax / 16;
123 const size_t resourceSize2 = sizeMax / 20;
124 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity);
125 ResourcePtr<FakeResource> cachedResource =
126 new FakeResource(ResourceRequest(""), Resource::Raw);
127 cachedResource->fakeEncodedSize(resourceSize1);
129 ASSERT_EQ(0u, memoryCache()->deadSize());
130 ASSERT_EQ(0u, memoryCache()->liveSize());
131 memoryCache()->add(cachedResource.get());
132 ASSERT_EQ(cachedResource->size(), memoryCache()->deadSize());
133 ASSERT_EQ(0u, memoryCache()->liveSize());
135 MockImageResourceClient client;
136 cachedResource->addClient(&client);
137 ASSERT_EQ(0u, memoryCache()->deadSize());
138 ASSERT_EQ(cachedResource->size(), memoryCache()->liveSize());
140 cachedResource->fakeEncodedSize(resourceSize2);
141 ASSERT_EQ(0u, memoryCache()->deadSize());
142 ASSERT_EQ(cachedResource->size(), memoryCache()->liveSize());
145 // Verifies that dead resources that exceed dead resource capacity are evicted
146 // from cache when pruning.
147 TEST_F(MemoryCacheTest, DeadResourceEviction)
149 memoryCache()->setDelayBeforeLiveDecodedPrune(0);
150 memoryCache()->setMaxPruneDeferralDelay(0);
151 const unsigned totalCapacity = 1000000;
152 const unsigned minDeadCapacity = 0;
153 const unsigned maxDeadCapacity = 0;
154 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity);
156 Resource* cachedResource =
157 new Resource(ResourceRequest(""), Resource::Raw);
158 const char data[5] = "abcd";
159 cachedResource->appendData(data, 3);
160 // The resource size has to be nonzero for this test to be meaningful, but
161 // we do not rely on it having any particular value.
162 ASSERT_GT(cachedResource->size(), 0u);
164 ASSERT_EQ(0u, memoryCache()->deadSize());
165 ASSERT_EQ(0u, memoryCache()->liveSize());
167 memoryCache()->add(cachedResource);
168 ASSERT_EQ(cachedResource->size(), memoryCache()->deadSize());
169 ASSERT_EQ(0u, memoryCache()->liveSize());
171 memoryCache()->prune();
172 ASSERT_EQ(0u, memoryCache()->deadSize());
173 ASSERT_EQ(0u, memoryCache()->liveSize());
176 // Verified that when ordering a prune in a runLoop task, the prune
177 // is deferred to the end of the task.
178 TEST_F(MemoryCacheTest, LiveResourceEvictionAtEndOfTask)
180 memoryCache()->setDelayBeforeLiveDecodedPrune(0);
181 const unsigned totalCapacity = 1;
182 const unsigned minDeadCapacity = 0;
183 const unsigned maxDeadCapacity = 0;
184 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity);
185 const char data[6] = "abcde";
186 Resource* cachedDeadResource =
187 new Resource(ResourceRequest("hhtp://foo"), Resource::Raw);
188 cachedDeadResource->appendData(data, 3);
189 ResourcePtr<Resource> cachedLiveResource =
190 new FakeDecodedResource(ResourceRequest(""), Resource::Raw);
191 MockImageResourceClient client;
192 cachedLiveResource->addClient(&client);
193 cachedLiveResource->appendData(data, 4);
195 class Task1 : public blink::WebThread::Task {
197 Task1(const ResourcePtr<Resource>& live, Resource* dead)
202 virtual void run() OVERRIDE
204 // The resource size has to be nonzero for this test to be meaningful, but
205 // we do not rely on it having any particular value.
206 ASSERT_GT(m_live->size(), 0u);
207 ASSERT_GT(m_dead->size(), 0u);
209 ASSERT_EQ(0u, memoryCache()->deadSize());
210 ASSERT_EQ(0u, memoryCache()->liveSize());
212 memoryCache()->add(m_dead);
213 memoryCache()->add(m_live.get());
214 memoryCache()->updateDecodedResource(m_live.get(), UpdateForPropertyChange);
215 ASSERT_EQ(m_dead->size(), memoryCache()->deadSize());
216 ASSERT_EQ(m_live->size(), memoryCache()->liveSize());
217 ASSERT_GT(m_live->decodedSize(), 0u);
219 memoryCache()->prune(); // Dead resources are pruned immediately
220 ASSERT_EQ(m_dead->size(), memoryCache()->deadSize());
221 ASSERT_EQ(m_live->size(), memoryCache()->liveSize());
222 ASSERT_GT(m_live->decodedSize(), 0u);
226 ResourcePtr<Resource> m_live;
230 class Task2 : public blink::WebThread::Task {
232 Task2(unsigned liveSizeWithoutDecode)
233 : m_liveSizeWithoutDecode(liveSizeWithoutDecode) { }
235 virtual void run() OVERRIDE
237 // Next task: now, the live resource was evicted.
238 ASSERT_EQ(0u, memoryCache()->deadSize());
239 ASSERT_EQ(m_liveSizeWithoutDecode, memoryCache()->liveSize());
240 blink::Platform::current()->currentThread()->exitRunLoop();
244 unsigned m_liveSizeWithoutDecode;
248 blink::Platform::current()->currentThread()->postTask(new Task1(cachedLiveResource, cachedDeadResource));
249 blink::Platform::current()->currentThread()->postTask(new Task2(cachedLiveResource->encodedSize() + cachedLiveResource->overheadSize()));
250 blink::Platform::current()->currentThread()->enterRunLoop();
251 cachedLiveResource->removeClient(&client);
254 // Verifies that cached resources are evicted immediately after release when
255 // the total dead resource size is more than double the dead resource capacity.
256 TEST_F(MemoryCacheTest, ClientRemoval)
258 const char data[6] = "abcde";
259 ResourcePtr<Resource> resource1 =
260 new FakeDecodedResource(ResourceRequest("http://foo.com"), Resource::Raw);
261 MockImageResourceClient client1;
262 resource1->addClient(&client1);
263 resource1->appendData(data, 4);
264 ResourcePtr<Resource> resource2 =
265 new FakeDecodedResource(ResourceRequest(""), Resource::Raw);
266 MockImageResourceClient client2;
267 resource2->addClient(&client2);
268 resource2->appendData(data, 4);
270 const unsigned minDeadCapacity = 0;
271 const unsigned maxDeadCapacity = ((resource1->size() + resource2->size()) / 2) - 1;
272 const unsigned totalCapacity = maxDeadCapacity;
273 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity);
274 memoryCache()->add(resource1.get());
275 memoryCache()->add(resource2.get());
276 // Call prune. There is nothing to prune, but this will initialize
277 // the prune timestamp, allowing future prunes to be deferred.
278 memoryCache()->prune();
279 ASSERT_GT(resource1->decodedSize(), 0u);
280 ASSERT_GT(resource2->decodedSize(), 0u);
281 ASSERT_EQ(memoryCache()->deadSize(), 0u);
282 ASSERT_EQ(memoryCache()->liveSize(), resource1->size() + resource2->size());
284 // Removing the client from resource1 should result in all resources
285 // remaining in cache since the prune is deferred.
286 resource1->removeClient(&client1);
287 ASSERT_GT(resource1->decodedSize(), 0u);
288 ASSERT_GT(resource2->decodedSize(), 0u);
289 ASSERT_EQ(memoryCache()->deadSize(), resource1->size());
290 ASSERT_EQ(memoryCache()->liveSize(), resource2->size());
291 ASSERT_TRUE(memoryCache()->contains(resource1.get()));
292 ASSERT_TRUE(memoryCache()->contains(resource2.get()));
294 // Removing the client from resource2 should result in immediate
295 // eviction of resource2 because we are over the prune deferral limit.
296 resource2->removeClient(&client2);
297 ASSERT_GT(resource1->decodedSize(), 0u);
298 ASSERT_GT(resource2->decodedSize(), 0u);
299 ASSERT_EQ(memoryCache()->deadSize(), resource1->size());
300 ASSERT_EQ(memoryCache()->liveSize(), 0u);
301 ASSERT_TRUE(memoryCache()->contains(resource1.get()));
302 ASSERT_FALSE(memoryCache()->contains(resource2.get()));
305 // Verifies that CachedResources are evicted from the decode cache
306 // according to their DecodeCachePriority.
307 TEST_F(MemoryCacheTest, DecodeCacheOrder)
309 memoryCache()->setDelayBeforeLiveDecodedPrune(0);
310 memoryCache()->setMaxPruneDeferralDelay(0);
311 ResourcePtr<FakeDecodedResource> cachedImageLowPriority =
312 new FakeDecodedResource(ResourceRequest("http://foo.com"), Resource::Raw);
313 ResourcePtr<FakeDecodedResource> cachedImageHighPriority =
314 new FakeDecodedResource(ResourceRequest(""), Resource::Raw);
316 MockImageResourceClient clientLowPriority;
317 MockImageResourceClient clientHighPriority;
318 cachedImageLowPriority->addClient(&clientLowPriority);
319 cachedImageHighPriority->addClient(&clientHighPriority);
321 const char data[5] = "abcd";
322 cachedImageLowPriority->appendData(data, 1);
323 cachedImageHighPriority->appendData(data, 4);
324 const unsigned lowPrioritySize = cachedImageLowPriority->size();
325 const unsigned highPrioritySize = cachedImageHighPriority->size();
326 const unsigned lowPriorityMockDecodeSize = cachedImageLowPriority->decodedSize();
327 const unsigned highPriorityMockDecodeSize = cachedImageHighPriority->decodedSize();
328 const unsigned totalSize = lowPrioritySize + highPrioritySize;
330 // Verify that the sizes are different to ensure that we can test eviction order.
331 ASSERT_GT(lowPrioritySize, 0u);
332 ASSERT_NE(lowPrioritySize, highPrioritySize);
333 ASSERT_GT(lowPriorityMockDecodeSize, 0u);
334 ASSERT_NE(lowPriorityMockDecodeSize, highPriorityMockDecodeSize);
336 ASSERT_EQ(memoryCache()->deadSize(), 0u);
337 ASSERT_EQ(memoryCache()->liveSize(), 0u);
339 // Add the items. The item added first would normally be evicted first.
340 memoryCache()->add(cachedImageHighPriority.get());
341 ASSERT_EQ(memoryCache()->deadSize(), 0u);
342 ASSERT_EQ(memoryCache()->liveSize(), highPrioritySize);
344 memoryCache()->add(cachedImageLowPriority.get());
345 ASSERT_EQ(memoryCache()->deadSize(), 0u);
346 ASSERT_EQ(memoryCache()->liveSize(), highPrioritySize + lowPrioritySize);
348 // Insert all items in the decoded items list with the same priority
349 memoryCache()->updateDecodedResource(cachedImageHighPriority.get(), UpdateForPropertyChange);
350 memoryCache()->updateDecodedResource(cachedImageLowPriority.get(), UpdateForPropertyChange);
351 ASSERT_EQ(memoryCache()->deadSize(), 0u);
352 ASSERT_EQ(memoryCache()->liveSize(), totalSize);
354 // Now we will assign their priority and make sure they are moved to the correct buckets.
355 memoryCache()->updateDecodedResource(cachedImageLowPriority.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityLow);
356 memoryCache()->updateDecodedResource(cachedImageHighPriority.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityHigh);
358 // Should first prune the LowPriority 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);
364 // Should prune the HighPriority item.
365 memoryCache()->setCapacities(memoryCache()->minDeadCapacity(), memoryCache()->liveSize() - 10, memoryCache()->liveSize() - 10);
366 memoryCache()->prune();
367 ASSERT_EQ(memoryCache()->deadSize(), 0u);
368 ASSERT_EQ(memoryCache()->liveSize(), totalSize - lowPriorityMockDecodeSize - highPriorityMockDecodeSize);
371 TEST_F(MemoryCacheTest, MultipleReplace)
373 ResourcePtr<FakeResource> resource1 = new FakeResource(ResourceRequest(""), Resource::Raw);
374 memoryCache()->add(resource1.get());
376 ResourcePtr<FakeResource> resource2 = new FakeResource(ResourceRequest(""), Resource::Raw);
377 memoryCache()->replace(resource2.get(), resource1.get());
378 EXPECT_TRUE(memoryCache()->contains(resource2.get()));
379 EXPECT_FALSE(memoryCache()->contains(resource1.get()));
381 ResourcePtr<FakeResource> resource3 = new FakeResource(ResourceRequest(""), Resource::Raw);
382 memoryCache()->replace(resource3.get(), resource2.get());
383 EXPECT_TRUE(memoryCache()->contains(resource3.get()));
384 EXPECT_FALSE(memoryCache()->contains(resource2.get()));
387 TEST_F(MemoryCacheTest, RemoveDuringRevalidation)
389 ResourcePtr<FakeResource> resource1 = new FakeResource(ResourceRequest(""), Resource::Raw);
390 memoryCache()->add(resource1.get());
392 ResourcePtr<FakeResource> resource2 = new FakeResource(ResourceRequest(""), Resource::Raw);
393 memoryCache()->remove(resource1.get());
394 memoryCache()->add(resource2.get());
395 EXPECT_TRUE(memoryCache()->contains(resource2.get()));
396 EXPECT_FALSE(memoryCache()->contains(resource1.get()));
398 ResourcePtr<FakeResource> resource3 = new FakeResource(ResourceRequest(""), Resource::Raw);
399 memoryCache()->remove(resource2.get());
400 memoryCache()->add(resource3.get());
401 EXPECT_TRUE(memoryCache()->contains(resource3.get()));
402 EXPECT_FALSE(memoryCache()->contains(resource2.get()));
404 memoryCache()->replace(resource1.get(), resource2.get());
405 EXPECT_TRUE(memoryCache()->contains(resource1.get()));
406 EXPECT_FALSE(memoryCache()->contains(resource2.get()));
407 EXPECT_FALSE(memoryCache()->contains(resource3.get()));