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