/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.github.geophile.erdo.immutableitemcache; import org.junit.Test; import java.io.IOException; import java.util.*; import static org.junit.Assert.*; public class ImmutableItemCacheTest { @Test public void testReadMissing() throws InterruptedException, IOException { final int CACHE_SIZE = 10; ImmutableItemCache<Id, Item> cache = new ImmutableItemCache<>(CACHE_SIZE); for (int i = 0; i < CACHE_SIZE; i++) { Id id = id(i); Item item = cache.find(id, ITEM_READER); assertEquals(id, item.id()); } } @Test public void testReadPresent() throws InterruptedException, IOException { final int CACHE_SIZE = 10; ImmutableItemCache<Id, Item> cache = new ImmutableItemCache<>(CACHE_SIZE); Item[] items = new Item[CACHE_SIZE]; // Load cache for (int i = 0; i < CACHE_SIZE; i++) { Id id = id(i); Item item = cache.find(id, ITEM_READER); assertEquals(id, item.id()); items[i] = item; } // Check that the identical items are retrieved for (int i = 0; i < CACHE_SIZE; i++) { Id id = id(i); Item item = cache.find(id, ITEM_READER); assertSame(items[i], item); } } @Test public void testOneEviction() throws InterruptedException, IOException { final int CACHE_SIZE = 10; EvictionObserver evictionObserver = new EvictionObserver(); ImmutableItemCache<Id, Item> cache = new ImmutableItemCache<>(CACHE_SIZE, evictionObserver); Item[] items = new Item[CACHE_SIZE]; // Load cache for (int i = 0; i < CACHE_SIZE; i++) { Id id = id(i); Item item = cache.find(id, ITEM_READER); assertEquals(id, item.id()); items[i] = item; } items[0].okToEvict(true); // Add one more, forcing eviction Item forceEviction = cache.find(id(CACHE_SIZE), ITEM_READER); assertEquals(id(CACHE_SIZE), forceEviction.id()); Map<Id, CacheEntry<Id, Item>> cacheContents = cache.cacheContents(); // Cache should contain all of the original items except what was evicted, and the item that forced eviction. assertSame(forceEviction, cacheContents.get(id(CACHE_SIZE))); for (Item item : items) { if (item != forceEviction) { Item cacheItem = (Item) cacheContents.get(item.id()); if (cacheItem == null) { Item victim = (Item) evictionObserver.evicted; assertEquals(victim.id(), item.id()); } else { assertSame(cacheItem, item); } } } } @Test public void testUnableToEvict() throws InterruptedException, IOException { final int CACHE_SIZE = 10; ImmutableItemCache<Id, Item> cache = new ImmutableItemCache<>(CACHE_SIZE); // Load cache and mark everything non-evictable int id = 0; while (id < CACHE_SIZE) { Item item = cache.find(id(id), ITEM_READER); item.okToEvict(false); id++; } try { cache.find(id(CACHE_SIZE), ITEM_READER); fail(); } catch (ImmutableItemCacheError e) { } } @Test public void testWithControlledEviction() throws InterruptedException, IOException { final int CACHE_SIZE = 10; final int OPS = 1000; EvictionObserver evictionObserver = new EvictionObserver(); ImmutableItemCache<Id, Item> cache = new ImmutableItemCache<>(CACHE_SIZE, evictionObserver); Random random = new Random(419); Set<Integer> expected = new HashSet<>(); // Load cache and mark everything non-evictable int id = 0; while (id < CACHE_SIZE) { Item item = cache.find(id(id), ITEM_READER); item.okToEvict(false); item.recentAccess(false); expected.add(id); id++; } // System.out.println(String.format("Starting: %s", expected)); // Operate on cache, tracking expected contents Map<Id, CacheEntry<Id, Item>> cacheContents = cache.cacheContents(); for (int i = 0; i < OPS; i++) { assertEquals(CACHE_SIZE, cacheContents.size()); // Make random item in cache evictable Iterator<CacheEntry<Id, Item>> iterator = cacheContents.values().iterator(); int r = random.nextInt(CACHE_SIZE); for (int j = 0; j < r; j++) { iterator.next(); } Item victim = (Item) iterator.next(); // System.out.print(String.format("evict %s, add %s -> ", victim.id().id, id)); boolean removed = expected.remove(victim.id().value()); assertTrue(removed); assertNotNull(victim); victim.okToEvict(true); // Get new cache item and check eviction Item newItem = cache.find(id(id), ITEM_READER); newItem.okToEvict(false); newItem.recentAccess(false); boolean added = expected.add(id); assertTrue(added); Item evicted = (Item) evictionObserver.evicted; assertSame(victim, evicted); id++; // System.out.println(expected); } List<Integer> expectedSorted = new ArrayList<>(expected); Collections.sort(expectedSorted); List<Integer> actualSorted = new ArrayList<>(); for (Id actualId : cacheContents.keySet()) { actualSorted.add(actualId.value()); } Collections.sort(actualSorted); assertEquals(expectedSorted, actualSorted); } private Id id(int id) { return new Id(id); } private static final ImmutableItemManager<Id, Item> ITEM_READER = new ImmutableItemManager<Id, Item>() { public Item getItemForCache(Id id) { return new Item(id); } @Override public void cleanupItemEvictedFromCache(Item item) throws IOException, InterruptedException { } }; private static class EvictionObserver implements CacheEntryList.Observer<Id, Item> { public void adding(Item item) { } public void evicting(Item victim) { evicted = victim; } public CacheEntry<Id, Item> evicted = null; } }