test.ca.odell.glazedlists.io.CachingListTest Maven / Gradle / Ivy
Show all versions of glazedlists_java15 Show documentation
/* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.io;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.event.ListEvent;
import junit.framework.TestCase;
import java.util.Random;
/**
* This test verifies that the {@link ca.odell.glazedlists.io.CachingList} behaves as expected.
*
* @author Kevin Maltby
*/
public class CachingListTest extends TestCase {
private CachingList cache = null;
private BasicEventList source = null;
/**
* Prepare for the test.
*/
public void setUp() {
source = new BasicEventList();
cache = new CachingList(source, 15);
}
/**
* Clean up after the test.
*/
public void tearDown() {
cache.dispose();
cache = null;
source.clear();
source = null;
}
/**
* Validate that the cache correctly retrieves values from the source
* and that there are zero cache hits.
*/
public void testNonRepetitiveLookups() {
// Load the source with data
for(int i = 0;i < 25;i++) {
source.add(new Integer(i));
}
// Lookup each value in the source
for(int i = 0;i < 25;i++) {
Object result = cache.get(i);
assertEquals(new Integer(i), (Integer)result);
}
// Verify that there have been no cache hits
assertEquals(0, cache.getCacheHits());
// Verify that there have been the correct number of cache misses
assertEquals(25, cache.getCacheMisses());
}
/**
* Validate that the cache correctly retrieves values from the source
* when fetched repeatedly. There should be one cache miss, and
* 24 cached hits.
*/
public void testRepetitiveLookup() {
// Load the source with some data
for(int i = 0;i < 3;i++) {
source.add(new Integer(i));
}
// Lookup the middle value in the source
Integer ONE = new Integer(1);
for(int i = 0;i < 25;i++) {
Object result = cache.get(1);
assertEquals(ONE, (Integer)result);
}
// Verify that there have been 24 cache hits
assertEquals(24, cache.getCacheHits());
// Verify that there have been only 1 cache miss
assertEquals(1, cache.getCacheMisses());
}
/**
* Validate that the cache behaves correctly when it has reached the defined
* maximum size and Objects which are uncached are requested. This tests
* cache overflow behaviour without repetitive lookups that can change which
* cache entry is considered the 'oldest' as that test is covered by
* {@link #testCacheEntryAccessReorderingForOverflow()}.
*/
public void testCacheOverflowBehaviour() {
// Load the source with data
for(int i = 0;i < 25;i++) {
source.add(new Integer(i));
}
// Lookup enough values to fill the cache
for(int i = 0;i < 15;i++) {
Object result = cache.get(i);
assertEquals(new Integer(i), (Integer)result);
}
int cacheHitBaseline = cache.getCacheHits();
int cacheMissBaseline = cache.getCacheMisses();
// Lookup one uncached object
Object result = cache.get(20);
assertEquals(new Integer(20), (Integer)result);
assertEquals(cacheHitBaseline, cache.getCacheHits());
assertEquals(cacheMissBaseline + 1, cache.getCacheMisses());
// Look for the first request value which should not be in the cache
result = cache.get(0);
assertEquals(new Integer(0), (Integer)result);
assertEquals(cacheHitBaseline, cache.getCacheHits());
assertEquals(cacheMissBaseline + 2, cache.getCacheMisses());
}
/**
* Validate that the cache reorders the cached entries correctly
* based on last access so that the oldest entry is removed when
* the cache overflows.
*/
public void testCacheEntryAccessReorderingForOverflow() {
// Load the source with data
for(int i = 0;i < 25;i++) {
source.add(new Integer(i));
}
// Lookup enough values to fill the cache
for(int i = 0;i < 15;i++) {
Object result = cache.get(i);
assertEquals(new Integer(i), (Integer)result);
}
// Randomly lookup cached data to randomize cache entry ordering
Random random = new Random(11);
for(int i = 0;i < 100;i++) {
cache.get(random.nextInt(15));
}
int cacheHitBaseline = cache.getCacheHits();
int cacheMissBaseline = cache.getCacheMisses();
// Lookup the cached values in reverse order to re-order entries in reverse
for(int i = 14;i >= 0;i--) {
Object result = cache.get(i);
assertEquals(new Integer(i), (Integer)result);
}
assertEquals(cacheHitBaseline += 15, cache.getCacheHits());
assertEquals(cacheMissBaseline, cache.getCacheMisses());
// Lookup one uncached object
Object result = cache.get(20);
assertEquals(new Integer(20), (Integer)result);
assertEquals(cacheHitBaseline, cache.getCacheHits());
assertEquals(cacheMissBaseline + 1, cache.getCacheMisses());
// Look for the previously cached values in reverse order
for(int i = 14;i >= 0;i--) {
result = cache.get(i);
assertEquals(new Integer(i), (Integer)result);
assertEquals(cacheHitBaseline, cache.getCacheHits());
assertEquals(cacheMissBaseline + 1 + (15 - i), cache.getCacheMisses());
}
}
/**
* Validates that when a remove is called on an index
* that is cached, the value is successfully removed
* from the cache.
*/
public void testRemovingCachedValueWithCachedFollower() {
// Load the source with some data
for(int i = 0;i < 10;i++) {
source.add(new Integer(i));
}
// Load some of the source data into the cache
for(int i = 0;i < 5;i++) {
cache.get(i);
}
// Remove data that is cached that has at least one
// element cached that follows it
cache.remove(2);
assertEquals(0, cache.getCacheHits());
assertEquals(5, cache.getCacheMisses());
Object result = cache.get(2);
assertEquals(3, ((Integer)result).intValue());
assertEquals(1, cache.getCacheHits());
assertEquals(5, cache.getCacheMisses());
}
/**
* Validates that when a remove is called on an index
* that is cached, the value is successfully removed
* from the cache.
*/
public void testRemovingCachedValueAtCacheEdge() {
// Load the source with some data
for(int i = 0;i < 10;i++) {
source.add(new Integer(i));
}
// Load some of the source data into the cache
for(int i = 0;i < 5;i++) {
cache.get(i);
}
// Remove at the edge of the cached data
cache.remove(4);
assertEquals(0, cache.getCacheHits());
assertEquals(5, cache.getCacheMisses());
Object result = cache.get(4);
assertEquals(5, ((Integer)result).intValue());
assertEquals(0, cache.getCacheHits());
assertEquals(6, cache.getCacheMisses());
}
/**
* Validates that when a remove is called on an index
* that has been cached, that the internal cache size
* decreases.
*/
public void testRemoveCorrectsCacheSize() {
// Load the source with data
for(int i = 0;i < 25;i++) {
source.add(new Integer(i));
}
// Lookup enough values to fill the cache
for(int i = 0;i < 15;i++) {
Object result = cache.get(i);
assertEquals(new Integer(i), (Integer)result);
}
// Remove 10 values leaving the first value
for(int i = 10; i > 0;i--) {
cache.remove(i);
}
// Refill cache
for(int i = 6; i < 15;i++) {
cache.get(i);
}
int cacheHitBaseline = cache.getCacheHits();
int cacheMissBaseline = cache.getCacheMisses();
// Lookup the first value which should still be the oldest
// in the cache if it resized correctly
Object result = cache.get(0);
assertEquals(0, ((Integer)result).intValue());
assertEquals(cacheHitBaseline + 1, cache.getCacheHits());
assertEquals(cacheMissBaseline, cache.getCacheMisses());
}
/**
* Validate the proper response to requested an index beyond
* source.size() - 1
. This test is included as
* a result of CachingList overriding get() from TransformedList.
*/
public void testBoundsErrorBehaviour() {
// request beyond bounds with an empty source
boolean exceptionThrown = false;
try {
cache.get(26);
} catch(IndexOutOfBoundsException e) {
exceptionThrown = true;
}
if(exceptionThrown == false) {
fail("No exception was thrown when an invalid index was requested on an empty source.");
}
// Load the source with data
for(int i = 0;i < 25;i++) {
source.add(new Integer(i));
}
// request beyond bounds with non-empty source and an empty cache
exceptionThrown = false;
try {
cache.get(26);
} catch(IndexOutOfBoundsException e) {
exceptionThrown = true;
}
if(exceptionThrown == false) {
fail("No exception was thrown when an invalid index was requested on an empty cache.");
}
// Lookup some values to paritally fill the cache
for(int i = 9;i < 15;i++) {
Object result = cache.get(i);
assertEquals(new Integer(i), (Integer)result);
}
// request beyond bounds with non-empty source and a partially filled cache
exceptionThrown = false;
try {
cache.get(26);
} catch(IndexOutOfBoundsException e) {
exceptionThrown = true;
}
if(exceptionThrown == false) {
fail("No exception was thrown when an invalid index was requested on a partially full cache.");
}
// Lookup enough values to fill the cache
for(int i = 9;i < 15;i++) {
Object result = cache.get(i);
assertEquals(new Integer(i), (Integer)result);
}
// request beyond bounds with non-empty source and a full cache
exceptionThrown = false;
try {
cache.get(26);
} catch(IndexOutOfBoundsException e) {
exceptionThrown = true;
}
if(!exceptionThrown) {
fail("No exception was thrown when an invalid index was requested on a full cache.");
}
}
/**
* This main provides performance testing capabilities for CachingList.
*
* It was originally the main method of CachingList itself, and it's since
* been moved here. It should be linked into the proper test system.
*/
public static void main(String[] args) {
int argsLength = args.length;
int listSize = -1;
int cacheSize = -1;
int waitDuration = -1;
CacheTestHelper testHelper = null;
// Parse the arguments to set up the performance tests.
if(argsLength == 2) {
try {
listSize = Integer.parseInt(args[0]);
waitDuration = Integer.parseInt(args[1]);
testHelper = new CacheTestHelper(listSize, waitDuration);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("CachingList expects integer arguments.");
}
} else if(argsLength == 3) {
try {
listSize = Integer.parseInt(args[0]);
cacheSize = Integer.parseInt(args[1]);
waitDuration = Integer.parseInt(args[2]);
testHelper = new CacheTestHelper(listSize, cacheSize, waitDuration);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("CachingList expects integer arguments.");
}
} else {
System.out.println("'CachingList' Usage:");
System.out.println("CachingList ");
System.out.println("OR");
System.out.println("CachingList ");
System.exit(1);
}
// Now run the tests
testHelper.runTests();
}
}
/**
* A simple extension of the BasicEventList to
* wait for an allotted time period before serving
* a request, emulating a more expensive call such
* as a database lookup.
*/
class WaitEventList extends TransformedList {
private int waitDuration = 0;
public WaitEventList(int waitDuration) {
super(new BasicEventList());
this.waitDuration = waitDuration;
}
public Object get(int index) {
try {
Thread.sleep(waitDuration);
return source.get(index);
} catch (Exception e) {
throw new RuntimeException("Thread.sleep() failure.", e);
}
}
/**
* For implementing the ListEventListener interface. When the underlying list
* changes, this sends notification to listening lists.
*/
public void listChanged(ListEvent listChanges) {
// just pass on the changes
updates.beginEvent();
while(listChanges.next()) {
updates.addChange(listChanges.getType(), listChanges.getIndex());
}
updates.commitEvent();
}
protected boolean isWritable() {
return true;
}
}
/**
* A helper class provided to simplify the included
* tests contained in main.
*/
class CacheTestHelper {
/** The variables to allow the test to be changed easily */
private int requestWaitTime = 0;
private int sourceListSize = 0;
private int cacheSize = 0;
private int interval = 0;
/** The CachingList to performance test */
private CachingList testCache = null;
/** The underlying EventList */
private EventList sourceList = null;
/**
* Creates a new CacheTestHelper to performance test the
* CachingList. This Constructor implicitly sizes the
* CachingList to be one-half the size of the source list.
*
* @param sourceListSize The size of the underlying list
* @param requestWaitTime The amount of time to delay when requesting
* information from the underlying list
*/
protected CacheTestHelper(int sourceListSize, int requestWaitTime) {
this.sourceListSize = sourceListSize;
this.requestWaitTime = requestWaitTime;
cacheSize = (int)Math.round(sourceListSize / 2.0);
interval = (int)Math.round(cacheSize / 10.0);
}
/**
* Creates a new CacheTestHelper to performance test the
* CachingList. This Constructor explicitly sizes the
* CachingList
*
* @param sourceListSize The size of the underlying list
* @param cacheSize The explicitly set size for the CachingList
* @param requestWaitTime The amount of time to delay when requesting
* information from the underlying list
*/
protected CacheTestHelper(int sourceListSize, int cacheSize, int requestWaitTime) {
this.sourceListSize = sourceListSize;
this.requestWaitTime = requestWaitTime;
this.cacheSize = cacheSize;
interval = (int)Math.round(cacheSize / 10.0);
}
/**
* Convenience method to allow the running of all of the
* tests defined in this helper class via a single method call
*/
protected void runTests() {
System.out.println("Beginning Performance Tests...");
// Set up the source list
sourceList = new WaitEventList(requestWaitTime);
for(int i = 0; i < sourceListSize; i++) {
sourceList.add(new Integer(i));
}
long startTime = System.currentTimeMillis();
testRetrievalFromCache();
setUp();
testRemoveOfCachedData();
tearDown();
setUp();
testRemoveOfUncachedData();
tearDown();
setUp();
testRandomInsertion();
tearDown();
setUp();
testClearingFullCache();
tearDown();
long endTime = System.currentTimeMillis();
long totalTime = endTime - startTime;
System.out.println("End of Tests");
System.out.println("Total Test Time: " + totalTime + "ms.");
}
/** The Particular Test Cases */
private void testRetrievalFromCache() {
for(int i = 0; i <= 10; i++) {
setUp();
performNGets(i * interval);
tearDown();
}
}
private void testRemoveOfCachedData() {
System.out.println("Testing the Expense of Removing " + cacheSize
+ " Cached Entries.");
long startTime = System.currentTimeMillis();
for(int i = 0; i < cacheSize; i++) {
sourceList.remove(0);
}
long endTime = System.currentTimeMillis();
long timeTaken = endTime - startTime;
System.out.println("Test completed in: " + timeTaken + "ms\n");
}
private void testRemoveOfUncachedData() {
System.out.println("Testing the Expense of Removing " + cacheSize
+ " Uncached Entries.");
long startTime = System.currentTimeMillis();
for(int i = cacheSize; i < sourceListSize; i++) {
sourceList.remove(cacheSize);
}
long endTime = System.currentTimeMillis();
long timeTaken = endTime - startTime;
System.out.println("Test completed in: " + timeTaken + "ms\n");
}
private void testRandomInsertion() {
Random random = new Random();
System.out.println("Testing the Expense of " + cacheSize + " Random Insertions.");
long startTime = System.currentTimeMillis();
for(int i = 0; i < cacheSize; i++) {
int index = random.nextInt(cacheSize);
sourceList.add(index, new Integer(index));
}
long endTime = System.currentTimeMillis();
long timeTaken = endTime - startTime;
System.out.println("Test completed in: " + timeTaken + "ms\n");
}
private void testClearingFullCache() {
System.out.println("Testing the Expense of Calling clear() on a Full Cache");
long startTime = System.currentTimeMillis();
testCache.clear();
long endTime = System.currentTimeMillis();
long timeTaken = endTime - startTime;
System.out.println("Test completed in: " + timeTaken + "ms\n");
}
/**
* Lookup from the cache starting at start
and going
* to the size of the cache
*
* @param start The index to start getting values from
*/
private void performNGets(int start) {
System.out.println("Running Test For A Cache Containing "
+ (100.0 * ((cacheSize - (float)start)/cacheSize))
+"% of the searched for values: ");
long startTime = System.currentTimeMillis();
for(int j = start; j < start + cacheSize; j++) {
testCache.get(j);
}
long endTime = System.currentTimeMillis();
long timeTaken = endTime - startTime;
int uncachedExpense = (testCache.getCacheMisses() - cacheSize) * requestWaitTime;
System.out.println("Percentage of cache used: "
+ (getCacheHitRatio() * 100.0) + "%");
System.out.println("Test completed in: " + timeTaken + "ms");
System.out.println("Cost of Uncached Information Retrieval: "
+ uncachedExpense + "ms");
System.out.println("Net Expense of using CachingList: "
+ Math.round(timeTaken - uncachedExpense) + "ms\n");
}
/**
* Prepare for a test.
*/
private void setUp() {
// Set up and preload the cache with the first items, up to cacheSize
testCache = new CachingList(sourceList, cacheSize);
for(int i = 0; i < cacheSize; i++) {
testCache.get(i);
}
}
/**
* Clean up after a test.
*/
private void tearDown() {
testCache = null;
}
/**
* Gets the ratio of cache hits to cache misses. This is a number between
* 0 and 1, where 0 means the cache is unused and 1 means the cache was
* used exclusively.
*
* @return The ratio of cache hits ot cache misses
*/
private float getCacheHitRatio() {
int cacheHits = testCache.getCacheHits();
int cacheMisses = testCache.getCacheMisses() - cacheSize;
if(cacheHits + cacheMisses == 0) return 0.0F;
return (float)cacheHits / (float)(cacheHits + cacheMisses);
}
}