// Verifies that CachedResources are evicted from the decode cache
// according to their DecodeCachePriority.
static void TestDecodeCacheOrder(const ResourcePtr<Resource>& cachedImageLowPriority, const ResourcePtr<Resource>& cachedImageHighPriority)
{
    memoryCache()->setDelayBeforeLiveDecodedPrune(0);
    memoryCache()->setMaxPruneDeferralDelay(0);

    MockImageResourceClient clientLowPriority(cachedImageLowPriority);
    MockImageResourceClient clientHighPriority(cachedImageHighPriority);

    const char data[5] = "abcd";
    cachedImageLowPriority->appendData(data, 1u);
    cachedImageHighPriority->appendData(data, 4u);
    const unsigned lowPrioritySize = cachedImageLowPriority->size();
    const unsigned highPrioritySize = cachedImageHighPriority->size();
    const unsigned lowPriorityMockDecodeSize = cachedImageLowPriority->decodedSize();
    const unsigned highPriorityMockDecodeSize = cachedImageHighPriority->decodedSize();
    const unsigned totalSize = lowPrioritySize + highPrioritySize;

    // Verify that the sizes are different to ensure that we can test eviction order.
    ASSERT_GT(lowPrioritySize, 0u);
    ASSERT_NE(lowPrioritySize, highPrioritySize);
    ASSERT_GT(lowPriorityMockDecodeSize, 0u);
    ASSERT_NE(lowPriorityMockDecodeSize, highPriorityMockDecodeSize);

    ASSERT_EQ(memoryCache()->deadSize(), 0u);
    ASSERT_EQ(memoryCache()->liveSize(), 0u);

    // Add the items. The item added first would normally be evicted first.
    memoryCache()->add(cachedImageHighPriority.get());
    ASSERT_EQ(memoryCache()->deadSize(), 0u);
    ASSERT_EQ(memoryCache()->liveSize(), highPrioritySize);

    memoryCache()->add(cachedImageLowPriority.get());
    ASSERT_EQ(memoryCache()->deadSize(), 0u);
    ASSERT_EQ(memoryCache()->liveSize(), highPrioritySize + lowPrioritySize);

    // Insert all items in the decoded items list with the same priority
    memoryCache()->updateDecodedResource(cachedImageHighPriority.get(), UpdateForPropertyChange);
    memoryCache()->updateDecodedResource(cachedImageLowPriority.get(), UpdateForPropertyChange);
    ASSERT_EQ(memoryCache()->deadSize(), 0u);
    ASSERT_EQ(memoryCache()->liveSize(), totalSize);

    // Now we will assign their priority and make sure they are moved to the correct buckets.
    memoryCache()->updateDecodedResource(cachedImageLowPriority.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityLow);
    memoryCache()->updateDecodedResource(cachedImageHighPriority.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityHigh);

    // Should first prune the LowPriority item.
    memoryCache()->setCapacities(memoryCache()->minDeadCapacity(), memoryCache()->liveSize() - 10, memoryCache()->liveSize() - 10);
    memoryCache()->prune();
    ASSERT_EQ(memoryCache()->deadSize(), 0u);
    ASSERT_EQ(memoryCache()->liveSize(), totalSize - lowPriorityMockDecodeSize);

    // Should prune the HighPriority item.
    memoryCache()->setCapacities(memoryCache()->minDeadCapacity(), memoryCache()->liveSize() - 10, memoryCache()->liveSize() - 10);
    memoryCache()->prune();
    ASSERT_EQ(memoryCache()->deadSize(), 0u);
    ASSERT_EQ(memoryCache()->liveSize(), totalSize - lowPriorityMockDecodeSize - highPriorityMockDecodeSize);
}
void MemoryCache::dumpLRULists(bool includeLive) const
{
    printf("LRU-SP lists in eviction order (Kilobytes decoded, Kilobytes encoded, Access count, Referenced, isPurgeable, wasPurged):\n");

    int size = m_allResources.size();
    for (int i = size - 1; i >= 0; i--) {
        printf("\n\nList %d: ", i);
        MemoryCacheEntry* current = m_allResources[i].m_tail;
        while (current) {
            ResourcePtr<Resource> currentResource = current->m_resource;
            if (includeLive || !currentResource->hasClients())
                printf("(%.1fK, %.1fK, %uA, %dR, %d, %d); ", currentResource->decodedSize() / 1024.0f, (currentResource->encodedSize() + currentResource->overheadSize()) / 1024.0f, current->m_accessCount, currentResource->hasClients(), currentResource->isPurgeable(), currentResource->wasPurged());

            current = current->m_previousInAllResourcesList;
        }
    }
}
// Verifies that cached resources are evicted immediately after release when
// the total dead resource size is more than double the dead resource capacity.
static void TestClientRemoval(const ResourcePtr<Resource>& resource1, const ResourcePtr<Resource>& resource2)
{
    const char data[6] = "abcde";
    MockImageResourceClient client1(resource1);
    resource1->appendData(data, 4u);
    MockImageResourceClient client2(resource2);
    resource2->appendData(data, 4u);

    const unsigned minDeadCapacity = 0;
    const unsigned maxDeadCapacity = ((resource1->size() + resource2->size()) / 2) - 1;
    const unsigned totalCapacity = maxDeadCapacity;
    memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity);
    memoryCache()->add(resource1.get());
    memoryCache()->add(resource2.get());
    // Call prune. There is nothing to prune, but this will initialize
    // the prune timestamp, allowing future prunes to be deferred.
    memoryCache()->prune();
    ASSERT_GT(resource1->decodedSize(), 0u);
    ASSERT_GT(resource2->decodedSize(), 0u);
    ASSERT_EQ(memoryCache()->deadSize(), 0u);
    ASSERT_EQ(memoryCache()->liveSize(), resource1->size() + resource2->size());

    // Removing the client from resource1 should result in all resources
    // remaining in cache since the prune is deferred.
    client1.removeAsClient();
    ASSERT_GT(resource1->decodedSize(), 0u);
    ASSERT_GT(resource2->decodedSize(), 0u);
    ASSERT_EQ(memoryCache()->deadSize(), resource1->size());
    ASSERT_EQ(memoryCache()->liveSize(), resource2->size());
    ASSERT_TRUE(memoryCache()->contains(resource1.get()));
    ASSERT_TRUE(memoryCache()->contains(resource2.get()));

    // Removing the client from resource2 should result in immediate
    // eviction of resource2 because we are over the prune deferral limit.
    client2.removeAsClient();
    ASSERT_GT(resource1->decodedSize(), 0u);
    ASSERT_GT(resource2->decodedSize(), 0u);
    ASSERT_EQ(memoryCache()->deadSize(), resource1->size());
    ASSERT_EQ(memoryCache()->liveSize(), 0u);
    ASSERT_TRUE(memoryCache()->contains(resource1.get()));
    ASSERT_FALSE(memoryCache()->contains(resource2.get()));
}
// Verifies that CachedResources are evicted from the decode cache
// according to their DecodeCachePriority.
TEST_F(MemoryCacheTest, DecodeCacheOrder)
{
    memoryCache()->setDelayBeforeLiveDecodedPrune(0);
    ResourcePtr<MockImageResource> cachedImageLowPriority =
        new MockImageResource(ResourceRequest(""), Resource::Raw);
    ResourcePtr<MockImageResource> cachedImageHighPriority =
        new MockImageResource(ResourceRequest(""), Resource::Raw);

    MockImageResourceClient clientLowPriority;
    MockImageResourceClient clientHighPriority;
    cachedImageLowPriority->addClient(&clientLowPriority);
    cachedImageHighPriority->addClient(&clientHighPriority);

    const char data[5] = "abcd";
    cachedImageLowPriority->appendData(data, 1);
    cachedImageHighPriority->appendData(data, 4);
    const unsigned lowPrioritySize = cachedImageLowPriority->size();
    const unsigned highPrioritySize = cachedImageHighPriority->size();
    const unsigned lowPriorityMockDecodeSize = cachedImageLowPriority->decodedSize();
    const unsigned highPriorityMockDecodeSize = cachedImageHighPriority->decodedSize();
    const unsigned totalSize = lowPrioritySize + highPrioritySize;

    // Verify that the sizes are different to ensure that we can test eviction order.
    ASSERT_GT(lowPrioritySize, 0u);
    ASSERT_NE(lowPrioritySize, highPrioritySize);
    ASSERT_GT(lowPriorityMockDecodeSize, 0u);
    ASSERT_NE(lowPriorityMockDecodeSize, highPriorityMockDecodeSize);

    ASSERT_EQ(memoryCache()->deadSize(), 0u);
    ASSERT_EQ(memoryCache()->liveSize(), 0u);

    // Add the items. The item added first would normally be evicted first.
    memoryCache()->add(cachedImageHighPriority.get());
    ASSERT_EQ(memoryCache()->deadSize(), 0u);
    ASSERT_EQ(memoryCache()->liveSize(), highPrioritySize);

    memoryCache()->add(cachedImageLowPriority.get());
    ASSERT_EQ(memoryCache()->deadSize(), 0u);
    ASSERT_EQ(memoryCache()->liveSize(), highPrioritySize + lowPrioritySize);

    // Insert all items in the decoded items list with the same priority
    memoryCache()->insertInLiveDecodedResourcesList(cachedImageHighPriority.get());
    memoryCache()->insertInLiveDecodedResourcesList(cachedImageLowPriority.get());
    ASSERT_EQ(memoryCache()->deadSize(), 0u);
    ASSERT_EQ(memoryCache()->liveSize(), totalSize);

    // Now we will assign their priority and make sure they are moved to the correct buckets.
    cachedImageLowPriority->setCacheLiveResourcePriority(Resource::CacheLiveResourcePriorityLow);
    cachedImageHighPriority->setCacheLiveResourcePriority(Resource::CacheLiveResourcePriorityHigh);

    // Should first prune the LowPriority item.
    memoryCache()->setCapacities(memoryCache()->minDeadCapacity(), memoryCache()->liveSize() - 10, memoryCache()->liveSize() - 10);
    memoryCache()->prune();
    ASSERT_EQ(memoryCache()->deadSize(), 0u);
    ASSERT_EQ(memoryCache()->liveSize(), totalSize - lowPriorityMockDecodeSize);

    // Should prune the HighPriority item.
    memoryCache()->setCapacities(memoryCache()->minDeadCapacity(), memoryCache()->liveSize() - 10, memoryCache()->liveSize() - 10);
    memoryCache()->prune();
    ASSERT_EQ(memoryCache()->deadSize(), 0u);
    ASSERT_EQ(memoryCache()->liveSize(), totalSize - lowPriorityMockDecodeSize - highPriorityMockDecodeSize);
}