/*
* Copyright (c) 2015, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.test.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import org.postgresql.util.CanEstimateSize;
import org.postgresql.util.LruCache;
import org.junit.Before;
import org.junit.Test;
import java.sql.SQLException;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
/**
* Tests {@link org.postgresql.util.LruCache}
*/
public class LruCacheTest {
private static class Entry implements CanEstimateSize {
private final int id;
Entry(int id) {
this.id = id;
}
@Override
public long getSize() {
return id;
}
@Override
public String toString() {
return "Entry{" + "id=" + id + '}';
}
}
private final Integer[] expectCreate = new Integer[1];
private final Deque<Entry> expectEvict = new ArrayDeque<Entry>();
private final Entry dummy = new Entry(-999);
private LruCache<Integer, Entry> cache;
@Before
public void setUp() throws Exception {
cache = new LruCache<Integer, Entry>(4, 1000, false, new LruCache.CreateAction<Integer, Entry>() {
@Override
public Entry create(Integer key) throws SQLException {
assertEquals("Unexpected create", expectCreate[0], key);
return new Entry(key);
}
}, new LruCache.EvictAction<Entry>() {
@Override
public void evict(Entry entry) throws SQLException {
if (expectEvict.isEmpty()) {
fail("Unexpected entry was evicted: " + entry);
}
Entry expected = expectEvict.removeFirst();
assertEquals("Unexpected evict", expected, entry);
}
});
}
@Test
public void testEvictsByNumberOfEntries() throws SQLException {
Entry a;
Entry b;
Entry c;
Entry d;
Entry e;
a = use(1);
b = use(2);
c = use(3);
d = use(4);
e = use(5, a);
}
@Test
public void testEvictsBySize() throws SQLException {
Entry a;
Entry b;
Entry c;
a = use(330);
b = use(331);
c = use(332);
use(400, a, b);
}
@Test
public void testEvictsLeastRecentlyUsed() throws SQLException {
Entry a;
Entry b;
Entry c;
Entry d;
a = use(1);
b = use(2);
c = use(3);
a = use(1); // reuse a
use(5);
d = use(4, b); // expect b to be evicted
}
@Test
public void testCyclicReplacement() throws SQLException {
Entry a;
Entry b;
Entry c;
Entry d;
Entry e;
a = use(1);
b = use(2);
c = use(3);
d = use(4);
e = use(5, a);
for (int i = 0; i < 1000; i++) {
a = use(1, b);
b = use(2, c);
c = use(3, d);
d = use(4, e);
e = use(5, a);
}
}
@Test
public void testDuplicateKey() throws SQLException {
Entry a;
a = use(1);
expectEvict.clear();
expectEvict.add(a);
// This overwrites the cache, evicting previous entry with exactly the same key
cache.put(1, new Entry(1));
assertEvict();
}
@Test
public void testCaching() throws SQLException {
Entry a;
Entry b;
Entry c;
Entry d;
Entry e;
a = use(1);
b = use(2);
c = use(3);
d = use(4);
for (int i = 0; i < 10000; i++) {
c = use(-3);
b = use(-2);
a = use(-1);
e = use(5, d);
c = use(-3);
b = use(-2);
a = use(-1);
d = use(4, e);
}
}
private Entry use(int expectCreate, Entry... expectEvict) throws SQLException {
this.expectCreate[0] = expectCreate <= 0 ? -1 : expectCreate;
this.expectEvict.clear();
this.expectEvict.addAll(Arrays.asList(expectEvict));
Entry a = cache.borrow(Math.abs(expectCreate));
cache.put(a.id, a); // a
assertEvict();
return a;
}
private void assertEvict() {
if (expectEvict.isEmpty()) {
return;
}
fail("Some of the expected evictions not happened: " + expectEvict.toString());
}
}