package org.jgroups.blocks;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.io.*;
import java.util.*;
public class CacheTest {
private static final String KEY1 = "key1";
private static final String VALUE1 = "value1";
private static final String KEY2 = "key2";
private static final String VALUE2 = "value2";
private static final String KEY3 = "key3";
private static final String VALUE3 = "value3";
@Test
public void testDefaultConstructor() {
// Run
Cache<String, String> cache = new Cache<>();
// Verify
Assert.assertFalse(cache.isReapingEnabled());
Assert.assertEquals(cache.getSize(), 0);
Assert.assertTrue(cache.entrySet().isEmpty());
Assert.assertEquals(cache.getMaxNumberOfEntries(), 0);
}
@Test
public void testGetValue() throws Exception {
// Setup
Cache<String, String> cache = new Cache<>();
cache.put(KEY1, VALUE1, 60000);
cache.put(KEY2, VALUE2, 10);
// Wait for KEY2 to expire before getting the values
Thread.sleep(100);
String value1 = cache.get(KEY1);
String value2 = cache.get(KEY2);
Assert.assertEquals(value1, VALUE1);
Assert.assertTrue(cache.getInternalMap().containsKey(KEY1));
// KEY2 should have been evicted and get() should return null
Assert.assertNull(value2);
Assert.assertFalse(cache.getInternalMap().containsKey(KEY2));
}
@Test
public void testRemoveValue() throws Exception {
// Setup
Cache<String, String> cache = new Cache<>();
cache.put(KEY1, VALUE1, 10000);
// Run
String value1 = cache.remove(KEY1);
// Verify
Assert.assertEquals(value1, VALUE1);
Assert.assertNull(cache.getEntry(KEY1));
}
@Test
public void testReaping() throws Exception {
// Setup
Cache<String, String> cache = new Cache<>();
TestChangeListener listener = new TestChangeListener(cache);
cache.addChangeListener(listener);
cache.enableReaping(100);
cache.start();
// Run
cache.put(KEY1, VALUE1, 250);
cache.put(KEY2, VALUE2, 500);
Thread.sleep(750);
cache.stop();
// Verify
Assert.assertEquals(listener.changes.size(), 2);
Assert.assertEquals(listener.changes.get(0).size, 1);
Assert.assertEquals(listener.changes.get(0).entries.iterator().next().getKey(), KEY2);
Assert.assertEquals(listener.changes.get(1).size, 0);
Assert.assertEquals(cache.getSize(), 0);
}
@Test
public void testChangeReapInterval() throws Exception {
// Setup
Cache<String, String> cache = new Cache<>();
cache.enableReaping(10000);
cache.start();
// Run
cache.put(KEY1, VALUE1, 200);
cache.enableReaping(50);
Thread.sleep(300);
cache.stop();
// Verify
Assert.assertEquals(cache.getSize(), 0);
}
@Test
public void testDisableReaping() throws Exception {
// Setup
Cache<String, String> cache = new Cache<>();
cache.enableReaping(500);
cache.start();
// Run
cache.put(KEY1, VALUE1, 200);
cache.disableReaping();
Thread.sleep(500);
cache.stop();
// Verify
Assert.assertFalse(cache.isReapingEnabled());
Assert.assertEquals(cache.getSize(), 1);
Assert.assertNotNull(cache.getEntry(KEY1));
}
@Test
public void testStopAndRestart() throws Exception {
// Setup
Cache<String, String> cache = new Cache<>();
cache.start();
cache.enableReaping(500);
// Run
cache.put(KEY1, VALUE1, 100);
// Stop the cache before reaping takes place
cache.stop();
// Wait long enough for reaping to have taken place
Thread.sleep(800);
// Verify the entry was not evicted
Assert.assertNotNull(cache.getEntry(KEY1));
// Restart and re-enable reaping
cache.start();
cache.enableReaping(100);
Thread.sleep(300);
// Verify entry was evicted
Assert.assertNull(cache.getEntry(KEY1));
}
@Test
public void testMaxEntries() throws Exception {
// Setup
Cache<String, String> cache = new Cache<>();
cache.setMaxNumberOfEntries(2);
// Run
cache.put(KEY1, VALUE1, 100);
cache.put(KEY2, VALUE2, 5000);
// Wait long enough for KEY1 to expire
Thread.sleep(200);
// Verify KEY1 is still in the cache
Assert.assertNotNull(cache.getEntry(KEY1));
// Add a third entry which exceeds the maximum number of entries
cache.put(KEY3, VALUE3, 5000);
// Wait for eviction to run
Thread.sleep(500);
// Verify KEY1 was evicted because it was expired
Assert.assertNull(cache.getEntry(KEY1));
// Verify the cache still contains 2 entries (KEY2 and KEY3)
Assert.assertEquals(cache.getSize(), 2);
// Add KEY1 back in so that the oldest entry (KEY2) is evicted
cache.put(KEY1, VALUE1, 5000);
// Wait for eviction to run
Thread.sleep(500);
// Verify KEY2 was evicted because it was the oldest
Assert.assertNull(cache.getEntry(KEY2));
// Verify the cache still contains 2 entries (KEY1 and KEY3)
Assert.assertEquals(cache.getSize(), 2);
}
@Test
public void testRemoveListener() throws Exception {
// Setup
Cache<String, String> cache = new Cache<>();
TestChangeListener listener = new TestChangeListener(cache);
cache.addChangeListener(listener);
cache.enableReaping(100);
cache.start();
// Run
cache.put(KEY1, VALUE1, 200);
cache.put(KEY2, VALUE2, 800);
Thread.sleep(500);
cache.removeChangeListener(listener);
Thread.sleep(500);
cache.stop();
// Listener should only have detected the eviction of KEY1
Assert.assertEquals(listener.changes.size(), 1);
// KEY2 should be in the cache after the first eviction
Assert.assertEquals(listener.changes.get(0).size, 1);
Assert.assertEquals(listener.changes.get(0).entries.iterator().next().getKey(), KEY2);
}
@Test
public void testListenerThrowsException() throws Exception {
// Setup
Cache<String, String> cache = new Cache<>();
// Listeners are notified in the order they are added, so add the bad listener first
// to ensure we throw an exception before the good listener is notified of changes
TestChangeListener goodListener = new TestChangeListener(cache);
cache.addChangeListener(new BadChangeListener());
cache.addChangeListener(goodListener);
cache.enableReaping(100);
cache.start();
// Run
cache.put(KEY1, VALUE1, 200);
Thread.sleep(400);
cache.stop();
// Good listener should have been notified that KEY1 was evicted
Assert.assertEquals(goodListener.changes.size(), 1);
}
@Test
public void testDumpAndToString() {
// Setup
Cache<String, String> cache = new Cache<>();
cache.put(KEY1, VALUE1, 1000);
cache.put(KEY2, VALUE2, 0);
// Run
String dump = cache.dump();
String toString = cache.toString();
// Verify
Assert.assertTrue(dump.contains("key1: value1\n"));
Assert.assertTrue(dump.contains("key2: value2\n"));
Assert.assertTrue(toString.contains("key1: value1 (expiration_time: "));
Assert.assertTrue(toString.contains("key2: value2 (expiration_time: 0)"));
}
@Test
public void testDumpWithBytes() {
// Setup
Cache<String, byte[]> cache = new Cache<>();
cache.put(KEY1, VALUE1.getBytes(), 1000);
cache.put(KEY2, VALUE2.getBytes(), 0);
// Run
String dump = cache.dump();
// Verify
Assert.assertTrue(dump.contains("key1: (6 bytes)\n"));
Assert.assertTrue(dump.contains("key2: (6 bytes)\n"));
}
@Test
public void testValueSerialization() throws Exception {
// Setup
Cache.Value<String> original = new Cache.Value<>(VALUE1, 1000);
Cache.Value<String> copy = new Cache.Value<>();
// Write the original to a ByteArrayOutputStream
ByteArrayOutputStream valueAsBytes = new ByteArrayOutputStream();
ObjectOutput out = new ObjectOutputStream(valueAsBytes);
original.writeExternal(out);
out.flush();
// Read the copy from a ByteArrayInputStream
long insertionTimeMin = System.currentTimeMillis();
ObjectInput in = new ObjectInputStream(new ByteArrayInputStream(valueAsBytes.toByteArray()));
copy.readExternal(in);
long insertionTimeMax = System.currentTimeMillis();
// Verify original and copy have the same values
Assert.assertEquals(copy.getValue(), original.getValue());
Assert.assertEquals(copy.getTimeout(), original.getTimeout());
Assert.assertTrue(copy.getInsertionTime() >= insertionTimeMin);
Assert.assertTrue(copy.getInsertionTime() <= insertionTimeMax);
}
private static final class CacheState {
final long timestamp;
final Set<Map.Entry> entries;
final int size;
final boolean isReaping;
public CacheState(Set<Map.Entry> entries, int size, boolean isReaping) {
this.timestamp = System.nanoTime();
this.size = size;
this.isReaping = isReaping;
this.entries = new HashSet<>(entries);
}
}
private static final class TestChangeListener implements Cache.ChangeListener {
final Cache cache;
final List<CacheState> changes = new ArrayList<>();
public TestChangeListener(Cache cache) {
this.cache = cache;
}
@Override
public void changed() {
changes.add(new CacheState(cache.entrySet(), cache.getSize(), cache.isReapingEnabled()));
}
}
private static final class BadChangeListener implements Cache.ChangeListener {
@Override
public void changed() {
throw new RuntimeException("Test exception");
}
}
}