| OLD | NEW |
| (Empty) |
| 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 "core/fetch/MemoryCache.h" | |
| 32 | |
| 33 #include "core/fetch/MockResourceClient.h" | |
| 34 #include "core/fetch/RawResource.h" | |
| 35 #include "platform/network/ResourceRequest.h" | |
| 36 #include "platform/testing/UnitTestHelpers.h" | |
| 37 #include "public/platform/Platform.h" | |
| 38 #include "testing/gtest/include/gtest/gtest.h" | |
| 39 | |
| 40 namespace blink { | |
| 41 | |
| 42 class MemoryCacheTest : public ::testing::Test { | |
| 43 public: | |
| 44 class FakeDecodedResource final : public Resource { | |
| 45 public: | |
| 46 static FakeDecodedResource* create(const ResourceRequest& request, | |
| 47 Type type) { | |
| 48 return new FakeDecodedResource(request, type, ResourceLoaderOptions()); | |
| 49 } | |
| 50 | |
| 51 virtual void appendData(const char* data, size_t len) { | |
| 52 Resource::appendData(data, len); | |
| 53 setDecodedSize(this->size()); | |
| 54 } | |
| 55 | |
| 56 private: | |
| 57 FakeDecodedResource(const ResourceRequest& request, | |
| 58 Type type, | |
| 59 const ResourceLoaderOptions& options) | |
| 60 : Resource(request, type, options) {} | |
| 61 | |
| 62 void destroyDecodedDataIfPossible() override { setDecodedSize(0); } | |
| 63 }; | |
| 64 | |
| 65 class FakeResource final : public Resource { | |
| 66 public: | |
| 67 static FakeResource* create(const ResourceRequest& request, Type type) { | |
| 68 return new FakeResource(request, type, ResourceLoaderOptions()); | |
| 69 } | |
| 70 | |
| 71 void fakeEncodedSize(size_t size) { setEncodedSize(size); } | |
| 72 | |
| 73 private: | |
| 74 FakeResource(const ResourceRequest& request, | |
| 75 Type type, | |
| 76 const ResourceLoaderOptions& options) | |
| 77 : Resource(request, type, options) {} | |
| 78 }; | |
| 79 | |
| 80 protected: | |
| 81 virtual void SetUp() { | |
| 82 // Save the global memory cache to restore it upon teardown. | |
| 83 m_globalMemoryCache = replaceMemoryCacheForTesting(MemoryCache::create()); | |
| 84 } | |
| 85 | |
| 86 virtual void TearDown() { | |
| 87 replaceMemoryCacheForTesting(m_globalMemoryCache.release()); | |
| 88 } | |
| 89 | |
| 90 Persistent<MemoryCache> m_globalMemoryCache; | |
| 91 }; | |
| 92 | |
| 93 // Verifies that setters and getters for cache capacities work correcty. | |
| 94 TEST_F(MemoryCacheTest, CapacityAccounting) { | |
| 95 const size_t sizeMax = ~static_cast<size_t>(0); | |
| 96 const size_t totalCapacity = sizeMax / 4; | |
| 97 memoryCache()->setCapacity(totalCapacity); | |
| 98 EXPECT_EQ(totalCapacity, memoryCache()->capacity()); | |
| 99 } | |
| 100 | |
| 101 TEST_F(MemoryCacheTest, VeryLargeResourceAccounting) { | |
| 102 const size_t sizeMax = ~static_cast<size_t>(0); | |
| 103 const size_t totalCapacity = sizeMax / 4; | |
| 104 const size_t resourceSize1 = sizeMax / 16; | |
| 105 const size_t resourceSize2 = sizeMax / 20; | |
| 106 memoryCache()->setCapacity(totalCapacity); | |
| 107 FakeResource* cachedResource = FakeResource::create( | |
| 108 ResourceRequest("http://test/resource"), Resource::Raw); | |
| 109 cachedResource->fakeEncodedSize(resourceSize1); | |
| 110 | |
| 111 EXPECT_EQ(0u, memoryCache()->size()); | |
| 112 memoryCache()->add(cachedResource); | |
| 113 EXPECT_EQ(cachedResource->size(), memoryCache()->size()); | |
| 114 | |
| 115 Persistent<MockResourceClient> client = | |
| 116 new MockResourceClient(cachedResource); | |
| 117 EXPECT_EQ(cachedResource->size(), memoryCache()->size()); | |
| 118 | |
| 119 cachedResource->fakeEncodedSize(resourceSize2); | |
| 120 EXPECT_EQ(cachedResource->size(), memoryCache()->size()); | |
| 121 } | |
| 122 | |
| 123 static void runTask1(Resource* resource1, Resource* resource2) { | |
| 124 // The resource size has to be nonzero for this test to be meaningful, but | |
| 125 // we do not rely on it having any particular value. | |
| 126 EXPECT_GT(resource1->size(), 0u); | |
| 127 EXPECT_GT(resource2->size(), 0u); | |
| 128 | |
| 129 EXPECT_EQ(0u, memoryCache()->size()); | |
| 130 | |
| 131 memoryCache()->add(resource1); | |
| 132 memoryCache()->add(resource2); | |
| 133 | |
| 134 size_t totalSize = resource1->size() + resource2->size(); | |
| 135 EXPECT_EQ(totalSize, memoryCache()->size()); | |
| 136 EXPECT_GT(resource1->decodedSize(), 0u); | |
| 137 EXPECT_GT(resource2->decodedSize(), 0u); | |
| 138 | |
| 139 // We expect actual pruning doesn't occur here synchronously but deferred | |
| 140 // to the end of this task, due to the previous pruning invoked in | |
| 141 // testResourcePruningAtEndOfTask(). | |
| 142 memoryCache()->prune(); | |
| 143 EXPECT_EQ(totalSize, memoryCache()->size()); | |
| 144 EXPECT_GT(resource1->decodedSize(), 0u); | |
| 145 EXPECT_GT(resource2->decodedSize(), 0u); | |
| 146 } | |
| 147 | |
| 148 static void runTask2(unsigned sizeWithoutDecode) { | |
| 149 // Next task: now, the resources was pruned. | |
| 150 EXPECT_EQ(sizeWithoutDecode, memoryCache()->size()); | |
| 151 } | |
| 152 | |
| 153 static void testResourcePruningAtEndOfTask(Resource* resource1, | |
| 154 Resource* resource2) { | |
| 155 memoryCache()->setDelayBeforeLiveDecodedPrune(0); | |
| 156 | |
| 157 // Enforce pruning by adding |dummyResource| and then call prune(). | |
| 158 Resource* dummyResource = | |
| 159 RawResource::create(ResourceRequest("http://dummy"), Resource::Raw); | |
| 160 memoryCache()->add(dummyResource); | |
| 161 EXPECT_GT(memoryCache()->size(), 1u); | |
| 162 const unsigned totalCapacity = 1; | |
| 163 memoryCache()->setCapacity(totalCapacity); | |
| 164 memoryCache()->prune(); | |
| 165 memoryCache()->remove(dummyResource); | |
| 166 EXPECT_EQ(0u, memoryCache()->size()); | |
| 167 | |
| 168 const char data[6] = "abcde"; | |
| 169 resource1->appendData(data, 3u); | |
| 170 resource1->finish(); | |
| 171 Persistent<MockResourceClient> client = new MockResourceClient(resource2); | |
| 172 resource2->appendData(data, 4u); | |
| 173 resource2->finish(); | |
| 174 | |
| 175 Platform::current()->currentThread()->getWebTaskRunner()->postTask( | |
| 176 BLINK_FROM_HERE, WTF::bind(&runTask1, wrapPersistent(resource1), | |
| 177 wrapPersistent(resource2))); | |
| 178 Platform::current()->currentThread()->getWebTaskRunner()->postTask( | |
| 179 BLINK_FROM_HERE, | |
| 180 WTF::bind(&runTask2, | |
| 181 resource1->encodedSize() + resource1->overheadSize() + | |
| 182 resource2->encodedSize() + resource2->overheadSize())); | |
| 183 testing::runPendingTasks(); | |
| 184 } | |
| 185 | |
| 186 // Verified that when ordering a prune in a runLoop task, the prune | |
| 187 // is deferred to the end of the task. | |
| 188 TEST_F(MemoryCacheTest, ResourcePruningAtEndOfTask_Basic) { | |
| 189 Resource* resource1 = FakeDecodedResource::create( | |
| 190 ResourceRequest("http://test/resource1"), Resource::Raw); | |
| 191 Resource* resource2 = FakeDecodedResource::create( | |
| 192 ResourceRequest("http://test/resource2"), Resource::Raw); | |
| 193 testResourcePruningAtEndOfTask(resource1, resource2); | |
| 194 } | |
| 195 | |
| 196 TEST_F(MemoryCacheTest, ResourcePruningAtEndOfTask_MultipleResourceMaps) { | |
| 197 { | |
| 198 Resource* resource1 = FakeDecodedResource::create( | |
| 199 ResourceRequest("http://test/resource1"), Resource::Raw); | |
| 200 Resource* resource2 = FakeDecodedResource::create( | |
| 201 ResourceRequest("http://test/resource2"), Resource::Raw); | |
| 202 resource1->setCacheIdentifier("foo"); | |
| 203 testResourcePruningAtEndOfTask(resource1, resource2); | |
| 204 memoryCache()->evictResources(); | |
| 205 } | |
| 206 { | |
| 207 Resource* resource1 = FakeDecodedResource::create( | |
| 208 ResourceRequest("http://test/resource1"), Resource::Raw); | |
| 209 Resource* resource2 = FakeDecodedResource::create( | |
| 210 ResourceRequest("http://test/resource2"), Resource::Raw); | |
| 211 resource1->setCacheIdentifier("foo"); | |
| 212 resource2->setCacheIdentifier("bar"); | |
| 213 testResourcePruningAtEndOfTask(resource1, resource2); | |
| 214 memoryCache()->evictResources(); | |
| 215 } | |
| 216 } | |
| 217 | |
| 218 // Verifies that | |
| 219 // - Resources are not pruned synchronously when ResourceClient is removed. | |
| 220 // - size() is updated appropriately when Resources are added to MemoryCache | |
| 221 // and garbage collected. | |
| 222 static void testClientRemoval(Resource* resource1, Resource* resource2) { | |
| 223 const char data[6] = "abcde"; | |
| 224 Persistent<MockResourceClient> client1 = new MockResourceClient(resource1); | |
| 225 resource1->appendData(data, 4u); | |
| 226 Persistent<MockResourceClient> client2 = new MockResourceClient(resource2); | |
| 227 resource2->appendData(data, 4u); | |
| 228 | |
| 229 memoryCache()->setCapacity(0); | |
| 230 memoryCache()->add(resource1); | |
| 231 memoryCache()->add(resource2); | |
| 232 | |
| 233 size_t originalTotalSize = resource1->size() + resource2->size(); | |
| 234 | |
| 235 // Call prune. There is nothing to prune, but this will initialize | |
| 236 // the prune timestamp, allowing future prunes to be deferred. | |
| 237 memoryCache()->prune(); | |
| 238 EXPECT_GT(resource1->decodedSize(), 0u); | |
| 239 EXPECT_GT(resource2->decodedSize(), 0u); | |
| 240 EXPECT_EQ(originalTotalSize, memoryCache()->size()); | |
| 241 | |
| 242 // Removing the client from resource1 should not trigger pruning. | |
| 243 client1->removeAsClient(); | |
| 244 EXPECT_GT(resource1->decodedSize(), 0u); | |
| 245 EXPECT_GT(resource2->decodedSize(), 0u); | |
| 246 EXPECT_EQ(originalTotalSize, memoryCache()->size()); | |
| 247 EXPECT_TRUE(memoryCache()->contains(resource1)); | |
| 248 EXPECT_TRUE(memoryCache()->contains(resource2)); | |
| 249 | |
| 250 // Removing the client from resource2 should not trigger pruning. | |
| 251 client2->removeAsClient(); | |
| 252 EXPECT_GT(resource1->decodedSize(), 0u); | |
| 253 EXPECT_GT(resource2->decodedSize(), 0u); | |
| 254 EXPECT_EQ(originalTotalSize, memoryCache()->size()); | |
| 255 EXPECT_TRUE(memoryCache()->contains(resource1)); | |
| 256 EXPECT_TRUE(memoryCache()->contains(resource2)); | |
| 257 | |
| 258 WeakPersistent<Resource> resource1Weak = resource1; | |
| 259 WeakPersistent<Resource> resource2Weak = resource2; | |
| 260 | |
| 261 ThreadState::current()->collectGarbage( | |
| 262 BlinkGC::NoHeapPointersOnStack, BlinkGC::GCWithSweep, BlinkGC::ForcedGC); | |
| 263 // Resources are garbage-collected (WeakMemoryCache) and thus removed | |
| 264 // from MemoryCache. | |
| 265 EXPECT_FALSE(resource1Weak); | |
| 266 EXPECT_FALSE(resource2Weak); | |
| 267 EXPECT_EQ(0u, memoryCache()->size()); | |
| 268 } | |
| 269 | |
| 270 TEST_F(MemoryCacheTest, ClientRemoval_Basic) { | |
| 271 Resource* resource1 = FakeDecodedResource::create( | |
| 272 ResourceRequest("http://foo.com"), Resource::Raw); | |
| 273 Resource* resource2 = FakeDecodedResource::create( | |
| 274 ResourceRequest("http://test/resource"), Resource::Raw); | |
| 275 testClientRemoval(resource1, resource2); | |
| 276 } | |
| 277 | |
| 278 TEST_F(MemoryCacheTest, ClientRemoval_MultipleResourceMaps) { | |
| 279 { | |
| 280 Resource* resource1 = FakeDecodedResource::create( | |
| 281 ResourceRequest("http://foo.com"), Resource::Raw); | |
| 282 resource1->setCacheIdentifier("foo"); | |
| 283 Resource* resource2 = FakeDecodedResource::create( | |
| 284 ResourceRequest("http://test/resource"), Resource::Raw); | |
| 285 testClientRemoval(resource1, resource2); | |
| 286 memoryCache()->evictResources(); | |
| 287 } | |
| 288 { | |
| 289 Resource* resource1 = FakeDecodedResource::create( | |
| 290 ResourceRequest("http://foo.com"), Resource::Raw); | |
| 291 Resource* resource2 = FakeDecodedResource::create( | |
| 292 ResourceRequest("http://test/resource"), Resource::Raw); | |
| 293 resource2->setCacheIdentifier("foo"); | |
| 294 testClientRemoval(resource1, resource2); | |
| 295 memoryCache()->evictResources(); | |
| 296 } | |
| 297 { | |
| 298 Resource* resource1 = FakeDecodedResource::create( | |
| 299 ResourceRequest("http://test/resource"), Resource::Raw); | |
| 300 resource1->setCacheIdentifier("foo"); | |
| 301 Resource* resource2 = FakeDecodedResource::create( | |
| 302 ResourceRequest("http://test/resource"), Resource::Raw); | |
| 303 resource2->setCacheIdentifier("bar"); | |
| 304 testClientRemoval(resource1, resource2); | |
| 305 memoryCache()->evictResources(); | |
| 306 } | |
| 307 } | |
| 308 | |
| 309 TEST_F(MemoryCacheTest, RemoveDuringRevalidation) { | |
| 310 FakeResource* resource1 = FakeResource::create( | |
| 311 ResourceRequest("http://test/resource"), Resource::Raw); | |
| 312 memoryCache()->add(resource1); | |
| 313 | |
| 314 FakeResource* resource2 = FakeResource::create( | |
| 315 ResourceRequest("http://test/resource"), Resource::Raw); | |
| 316 memoryCache()->remove(resource1); | |
| 317 memoryCache()->add(resource2); | |
| 318 EXPECT_TRUE(memoryCache()->contains(resource2)); | |
| 319 EXPECT_FALSE(memoryCache()->contains(resource1)); | |
| 320 | |
| 321 FakeResource* resource3 = FakeResource::create( | |
| 322 ResourceRequest("http://test/resource"), Resource::Raw); | |
| 323 memoryCache()->remove(resource2); | |
| 324 memoryCache()->add(resource3); | |
| 325 EXPECT_TRUE(memoryCache()->contains(resource3)); | |
| 326 EXPECT_FALSE(memoryCache()->contains(resource2)); | |
| 327 } | |
| 328 | |
| 329 TEST_F(MemoryCacheTest, ResourceMapIsolation) { | |
| 330 FakeResource* resource1 = FakeResource::create( | |
| 331 ResourceRequest("http://test/resource"), Resource::Raw); | |
| 332 memoryCache()->add(resource1); | |
| 333 | |
| 334 FakeResource* resource2 = FakeResource::create( | |
| 335 ResourceRequest("http://test/resource"), Resource::Raw); | |
| 336 resource2->setCacheIdentifier("foo"); | |
| 337 memoryCache()->add(resource2); | |
| 338 EXPECT_TRUE(memoryCache()->contains(resource1)); | |
| 339 EXPECT_TRUE(memoryCache()->contains(resource2)); | |
| 340 | |
| 341 const KURL url = KURL(ParsedURLString, "http://test/resource"); | |
| 342 EXPECT_EQ(resource1, memoryCache()->resourceForURL(url)); | |
| 343 EXPECT_EQ(resource1, memoryCache()->resourceForURL( | |
| 344 url, memoryCache()->defaultCacheIdentifier())); | |
| 345 EXPECT_EQ(resource2, memoryCache()->resourceForURL(url, "foo")); | |
| 346 EXPECT_EQ(0, memoryCache()->resourceForURL(KURL())); | |
| 347 | |
| 348 FakeResource* resource3 = FakeResource::create( | |
| 349 ResourceRequest("http://test/resource"), Resource::Raw); | |
| 350 resource3->setCacheIdentifier("foo"); | |
| 351 memoryCache()->remove(resource2); | |
| 352 memoryCache()->add(resource3); | |
| 353 EXPECT_TRUE(memoryCache()->contains(resource1)); | |
| 354 EXPECT_FALSE(memoryCache()->contains(resource2)); | |
| 355 EXPECT_TRUE(memoryCache()->contains(resource3)); | |
| 356 | |
| 357 HeapVector<Member<Resource>> resources = memoryCache()->resourcesForURL(url); | |
| 358 EXPECT_EQ(2u, resources.size()); | |
| 359 | |
| 360 memoryCache()->evictResources(); | |
| 361 EXPECT_FALSE(memoryCache()->contains(resource1)); | |
| 362 EXPECT_FALSE(memoryCache()->contains(resource3)); | |
| 363 } | |
| 364 | |
| 365 TEST_F(MemoryCacheTest, FragmentIdentifier) { | |
| 366 const KURL url1 = KURL(ParsedURLString, "http://test/resource#foo"); | |
| 367 FakeResource* resource = | |
| 368 FakeResource::create(ResourceRequest(url1), Resource::Raw); | |
| 369 memoryCache()->add(resource); | |
| 370 EXPECT_TRUE(memoryCache()->contains(resource)); | |
| 371 | |
| 372 EXPECT_EQ(resource, memoryCache()->resourceForURL(url1)); | |
| 373 | |
| 374 const KURL url2 = MemoryCache::removeFragmentIdentifierIfNeeded(url1); | |
| 375 EXPECT_EQ(resource, memoryCache()->resourceForURL(url2)); | |
| 376 } | |
| 377 | |
| 378 TEST_F(MemoryCacheTest, RemoveURLFromCache) { | |
| 379 const KURL url1 = KURL(ParsedURLString, "http://test/resource1"); | |
| 380 Persistent<FakeResource> resource1 = | |
| 381 FakeResource::create(ResourceRequest(url1), Resource::Raw); | |
| 382 memoryCache()->add(resource1); | |
| 383 EXPECT_TRUE(memoryCache()->contains(resource1)); | |
| 384 | |
| 385 memoryCache()->removeURLFromCache(url1); | |
| 386 EXPECT_FALSE(memoryCache()->contains(resource1)); | |
| 387 | |
| 388 const KURL url2 = KURL(ParsedURLString, "http://test/resource2#foo"); | |
| 389 FakeResource* resource2 = | |
| 390 FakeResource::create(ResourceRequest(url2), Resource::Raw); | |
| 391 memoryCache()->add(resource2); | |
| 392 EXPECT_TRUE(memoryCache()->contains(resource2)); | |
| 393 | |
| 394 memoryCache()->removeURLFromCache(url2); | |
| 395 EXPECT_FALSE(memoryCache()->contains(resource2)); | |
| 396 } | |
| 397 | |
| 398 } // namespace blink | |
| OLD | NEW |