package org.javersion.util;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.lang3.mutable.MutableInt;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
public abstract class AbstractPersistentMapTest<M extends PersistentMap<Integer, Integer>>
extends AbstractCollectionTest {
@Test
public void Empty_Map() {
PersistentMap<Integer, Integer> pmap = emptyMap();
assertThat(pmap.size(), equalTo(0));
assertThat(pmap.containsKey("key"), equalTo(false));
assertThat(pmap.containsKey(1), equalTo(false));
assertThat(pmap.iterator(), not(nullValue()));
assertThat(pmap.iterator().hasNext(), equalTo(false));
assertThat(pmap.dissoc(1), sameInstance(pmap));
assertThat(pmap.asMap(), equalTo((Map<Integer, Integer>) Maps.<Integer, Integer>newHashMap()));
}
@Test
public void Ascending() {
assertInsertAndDelete(ascending(10));
}
@Test
public void Ascending_Bulk_Insert() {
assertBulkInsert(ascending(345));
}
private void assertBulkInsert(List<Integer> ints) {
Map<Integer, Integer> map = Maps.newHashMapWithExpectedSize(ints.size());
for (Integer kv : ints) {
map.put(kv, kv);
}
PersistentMap<Integer, Integer> empty = emptyMap();
PersistentMap<Integer, Integer> pmap = empty.assocAll(map);
assertEmptyMap(empty);
assertThat(pmap.asMap(), equalTo(map));
for (Integer kv : ints) {
assertThat(pmap.get(kv), equalTo(kv));
}
assertMapProperties(pmap);
}
@Test
public void Descending_Bulk_Insert() {
assertBulkInsert(descending(300));
}
@Test
public void Descending() {
assertInsertAndDelete(descending(300));
}
@Test
public void Random() {
try {
assertInsertAndDelete(randoms(500));
} catch (AssertionError e) {
throw new AssertionError(DESC, e);
}
}
protected abstract M emptyMap();
@Test
public void Re_Insertions() {
List<Integer> ints = randoms(10);
PersistentMap<Integer, Integer> map = emptyMap();
for (int i=0; i < 3; i++) {
for (Integer kv : ints) {
map = map.assoc(kv, kv);
}
}
assertThat(map.size(), equalTo(10));
for (Integer kv : ints) {
assertThat(map.get(kv), equalTo(kv));
}
assertMapProperties(map);
}
@Test
public void Random_Bulk_Insert() {
try {
assertBulkInsert(randoms(300));
} catch (AssertionError e) {
throw new AssertionError(DESC, e);
}
}
@Test
public void Find_From_Empty_Map() {
PersistentMap<Integer, Integer> map = emptyMap();
assertThat(map.get(null), nullValue());
assertThat(map.get(1), nullValue());
}
@Test
public void Missing_Keys() {
PersistentMap<Integer, Integer> pmap = emptyMap();
for (int i=2; i < 100; i+=2) {
Integer kv = i*5;
pmap = pmap.assoc(kv, kv);
}
for (int i=1; i < 102; i+=2) {
Integer kv = i*5;
assertThat(pmap.get(kv), nullValue());
assertThat(pmap.dissoc(kv), sameInstance(pmap));
}
}
@Test(expected=NoSuchElementException.class)
public void Iterate_Empty() {
Iterator<Map.Entry<Integer, Integer>> iter = emptyMap().iterator();
assertThat(iter.hasNext(), equalTo(false));
iter.next();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
public void Merger_Gets_Called() {
Merger<Entry<Integer, Integer>> merger = mock(Merger.class);
doReturn(true).when(merger).insert(any(Entry.class));
doReturn(true).when(merger).delete(any(Entry.class));
doReturn(true).when(merger).merge(any(Entry.class), any(Entry.class));
ArgumentCaptor<Entry> entry1 = ArgumentCaptor.forClass(Entry.class);
ArgumentCaptor<Entry> entry2 = ArgumentCaptor.forClass(Entry.class);
PersistentMap<Integer, Integer> map = emptyMap();
map = map.merge(1, 1, merger);
assertThat(map.get(1), equalTo(1));
verify(merger).insert(entry1.capture());
assertEntry(entry1, 1, 1);
map = map.merge(1, 2, merger);
assertThat(map.get(1), equalTo(2));
verify(merger).merge(entry1.capture(), entry2.capture());
assertEntry(entry1, 1, 1);
assertEntry(entry2, 1, 2);
map = map.dissoc(1, merger);
assertThat(map.get(1), nullValue());
verify(merger).delete(entry2.capture());
assertEntry(entry2, 1, 2);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
public void Merge_All_Map() {
PersistentMap<Integer, Integer> map = emptyMap().assoc(1, 1);
Map<Integer, Integer> ints = ImmutableMap.of(1, 2, 3, 3);
Map<Integer, Integer> expected = ImmutableMap.of(1, 2, 3, 3);
Merger<Entry<Integer, Integer>> merger = mock(Merger.class);
doReturn(true).when(merger).insert(any(Entry.class));
doReturn(true).when(merger).merge(any(Entry.class), any(Entry.class));
map = map.mergeAll(ints, merger);
assertThat(map.asMap(), equalTo(expected));
ArgumentCaptor<Entry> entry1 = ArgumentCaptor.forClass(Entry.class);
ArgumentCaptor<Entry> entry2 = ArgumentCaptor.forClass(Entry.class);
verify(merger).merge(entry1.capture(), entry2.capture());
assertEntry(entry1, 1, 1);
assertEntry(entry2, 1, 2);
verify(merger).insert(entry1.capture());
assertEntry(entry1,3, 3);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
public void Merge_And_Keep_Old_Entry() {
PersistentMap<Integer, Integer> map = emptyMap().assoc(1, 1);
PersistentMap<Integer, Integer> ints = emptyMap().assoc(1, 2).assoc(3, 3);
Map<Integer, Integer> expected = ImmutableMap.of(1, 1, 3, 3);
ArgumentCaptor<Entry> entry1 = ArgumentCaptor.forClass(Entry.class);
ArgumentCaptor<Entry> entry2 = ArgumentCaptor.forClass(Entry.class);
Merger<Entry<Integer, Integer>> merger = mock(Merger.class);
doReturn(true).when(merger).insert(any(Entry.class));
doReturn(false).when(merger).merge(any(Entry.class), any(Entry.class));
map = map.mergeAll(ints, merger);
assertThat(map.asMap(), equalTo(expected));
assertThat(map.get(1), equalTo(1));
verify(merger).merge(entry1.capture(), entry2.capture());
assertEntry(entry1, 1, 1);
assertEntry(entry2, 1, 2);
verify(merger).insert(entry1.capture());
assertEntry(entry1, 3, 3);
}
@Test
public void veto_insert() {
final Merger<Entry<Integer, Integer>> merger = new Merger<Entry<Integer, Integer>>() {
@Override
public boolean insert(Entry<Integer, Integer> newEntry) {
return false;
}
};
PersistentMap<Integer, Integer> map = emptyMap();
PersistentMap<Integer, Integer> result = map.merge(1, 1, merger);
assertThat(result, sameInstance(map));
assertThat(result.isEmpty(), equalTo(true));
map = map.assoc(1, 1);
result = map.merge(2, 2, merger);
assertThat(result, sameInstance(map));
assertThat(result.size(), equalTo(1));
}
@Test
public void veto_delete() {
PersistentMap<Integer, Integer> map = emptyMap().assoc(1, 1);
PersistentMap<Integer, Integer> result = map.dissoc(1, new Merger<Entry<Integer, Integer>>() {
@Override
public boolean delete(Entry<Integer, Integer> newEntry) {
return false;
}
});
assertThat(result, sameInstance(map));
assertThat(result.isEmpty(), equalTo(false));
}
@Test
public void Editing_MutableMap_After_Committed_Doesnt_Affect_PersistedMap() {
PersistentMap<Integer, Integer> map = emptyMap().assoc(1, 1);
MutableMap<Integer, Integer> mutableMap = map.toMutableMap();
assertThat(map.get(1), equalTo(1));
// Editing
mutableMap.put(2, 2);
assertThat(map.containsKey(2), equalTo(false));
assertThat(mutableMap.containsKey(2), equalTo(true));
}
@Test(expected=IllegalStateException.class)
public void Edit_MutableMap_From_Another_Thread() throws Throwable {
final MutableMap<Integer, Integer> map = emptyMap().toMutableMap();
final AtomicReference<Throwable> exception = new AtomicReference<>();
final CountDownLatch countDown = new CountDownLatch(1);
new Thread() {
public void run() {
// This should throw IllegalStateException!
try {
map.put(1, 2);
} catch (Throwable t) {
exception.set(t);
} finally {
countDown.countDown();
}
}
}.start();
try {
countDown.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
assertThat(exception.get(), notNullValue());
throw exception.get();
}
@Test
public void assocAll() {
PersistentMap<Integer, Integer> base = emptyMap().assoc(1, 1);
PersistentMap<Integer, Integer> additional = emptyMap().assoc(2, 2).assoc(3, 3);
PersistentMap<Integer, Integer> sum = base.assocAll(additional);
assertThat(sum.size(), equalTo(3));
assertThat(sum.get(1), equalTo(1));
assertThat(sum.get(2), equalTo(2));
assertThat(sum.get(3), equalTo(3));
}
@Test
public void adding_duplicate_entry_returns_same_instance() {
PersistentMap<Integer, Integer> map = emptyMap().assoc(1, 1);
assertThat(map.assoc(1, 1), sameInstance(map));
}
@Test
public void entry_spliterator() {
PersistentMap<Integer, Integer> map = emptyMap();
map.spliterator().tryAdvance(i-> {
throw new RuntimeException();
});
map = map.assoc(1, 1).assoc(2, 2).assoc(3, 3).assoc(4, 4);
Spliterator<Entry<Integer, Integer>> spliterator = map.spliterator();
Spliterator<Entry<Integer, Integer>> split = spliterator.trySplit();
MutableInt sum = new MutableInt(0);
spliterator.forEachRemaining(entry -> sum.add(entry.getValue()));
split.forEachRemaining(entry -> sum.add(entry.getValue()));
assertThat(sum.intValue(), equalTo(10));
}
@Test
public void value_spliterator() {
PersistentMap<Integer, Integer> map = emptyMap();
map.valueSpliterator().tryAdvance(i-> {
throw new RuntimeException();
});
map = map.assoc(1, 1).assoc(2, 2).assoc(3, 3).assoc(4, 4);
Spliterator<Integer> spliterator = map.valueSpliterator();
Spliterator<Integer> split = spliterator.trySplit();
MutableInt sum = new MutableInt(0);
spliterator.forEachRemaining(value -> sum.add(value));
split.forEachRemaining(value -> sum.add(value));
assertThat(sum.intValue(), equalTo(10));
}
@Test
public void key_spliterator() {
PersistentMap<Integer, Integer> map = emptyMap();
map.keySpliterator().tryAdvance(i-> {
throw new RuntimeException();
});
map = map.assoc(1, 1).assoc(2, 2).assoc(3, 3).assoc(4, 4);
Spliterator<Integer> spliterator = map.keySpliterator();
Spliterator<Integer> split = spliterator.trySplit();
MutableInt sum = new MutableInt(0);
spliterator.forEachRemaining(value -> sum.add(value));
split.forEachRemaining(value -> sum.add(value));
assertThat(sum.intValue(), equalTo(10));
}
@Test(expected = UnsupportedOperationException.class)
public void unmodifiable_entries() {
PersistentMap<Integer, Integer> map = emptyMap().assoc(1, 1);
map.iterator().next().setValue(2);
}
@Test
public void iterate_empty() {
assertThat(emptyMap().iterator().hasNext(), equalTo(false));
}
@Test(expected = NoSuchElementException.class)
public void iterate_empty_no_such_element() {
emptyMap().iterator().next();
}
@Test
public void iterate_one() {
Iterator<Entry<Integer, Integer>> iter = emptyMap().assoc(1, 2).iterator();
assertThat(iter.hasNext(), equalTo(true));
Entry<Integer, Integer> entry = iter.next();
assertThat(entry.getKey(), equalTo(1));
assertThat(entry.getValue(), equalTo(2));
}
@Test(expected = NoSuchElementException.class)
public void iterate_one_no_such_element() {
Iterator<Entry<Integer, Integer>> iter = emptyMap().assoc(1, 1).iterator();
iter.next();
iter.next();
}
@SuppressWarnings({ "rawtypes" })
private void assertEntry(ArgumentCaptor<Entry> argument, Object key, Object value) {
assertThat(argument.getValue().getKey(), equalTo(key));
assertThat(argument.getValue().getValue(), equalTo(value));
}
protected void assertInsert(Integer... ints) {
assertInsertAndDelete(Arrays.asList(ints));
}
protected void assertInsertAndDelete(List<Integer> ints) {
PersistentMap<Integer, Integer> map = emptyMap();
List<PersistentMap<Integer, Integer>> maps = new ArrayList<>(ints.size());
for (Integer i : ints) {
map = map.assoc(i, i);
maps.add(map);
}
assertPersistentMaps(maps, ints);
PersistentMap<Integer, Integer> map2 = map;
for (Integer i : ints) {
map2 = map2.dissoc(i);
assertMapProperties(map2);
}
for (int i = ints.size() - 1; i > 0; i--) {
Integer key = ints.get(i);
map = map.dissoc(key);
maps.set(i-1, map);
}
assertPersistentMaps(maps, ints);
}
protected void assertPersistentMaps(
List<PersistentMap<Integer, Integer>> maps,
List<Integer> ints) {
for (int i=0; i < ints.size(); i++) {
Map<Integer, Integer> map = Maps.newHashMap();
PersistentMap<Integer, Integer> pmap = maps.get(i);
assertMapProperties(pmap);
assertThat(pmap.size(), equalTo(i+1));
for (int j=0; j < ints.size(); j++) {
Integer key = ints.get(j);
// Contains all values of previous maps
if (j <= i) {
assertThat(pmap.get(key), equalTo(key));
map.put(key, key);
}
// But none of the later values
else {
assertThat(pmap.get(key), nullValue());
}
}
assertThat(pmap.asMap(), equalTo(map));
}
}
protected abstract void assertMapProperties(PersistentMap<Integer, Integer> map);
protected abstract void assertEmptyMap(PersistentMap<Integer, Integer> map);
}