package org.infinispan.functional; import static org.infinispan.functional.FunctionalListenerAssertions.TestType.CREATE; import static org.infinispan.functional.FunctionalListenerAssertions.TestType.MODIFY; import static org.infinispan.functional.FunctionalListenerAssertions.TestType.REMOVE; import static org.infinispan.functional.FunctionalListenerAssertions.TestType.WRITE; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.concurrent.ConcurrentMap; import java.util.function.Consumer; import java.util.stream.IntStream; import javax.cache.Cache; import org.infinispan.commons.api.functional.EntryView.ReadEntryView; import org.infinispan.commons.api.functional.Listeners; import org.infinispan.functional.decorators.FunctionalListeners; public class FunctionalListenerAssertions<K, V> implements AutoCloseable { public enum TestType { CREATE, MODIFY, REMOVE, WRITE; private InternalTestType lambdaType() { switch (this) { case CREATE: return InternalTestType.LAMBDA_CREATE; case MODIFY: return InternalTestType.LAMBDA_MODIFY; case REMOVE: return InternalTestType.LAMBDA_REMOVE; case WRITE: return InternalTestType.LAMBDA_WRITE; } return null; } private InternalTestType listenerType() { switch (this) { case CREATE: return InternalTestType.LISTENER_CREATE; case MODIFY: return InternalTestType.LISTENER_MODIFY; case REMOVE: return InternalTestType.LISTENER_REMOVE; case WRITE: return InternalTestType.LISTENER_WRITE; } return null; } } private enum InternalTestType { LAMBDA_CREATE, LAMBDA_MODIFY, LAMBDA_REMOVE, LISTENER_CREATE, LISTENER_MODIFY, LISTENER_REMOVE, LAMBDA_WRITE, LISTENER_WRITE } final FunctionalListeners<K, V> listeners; final Runnable runnable; final List<TestEvent<V>> recorded = new ArrayList<>(); final List<AutoCloseable> closeables = new ArrayList<>(); @SuppressWarnings("unchecked") public static <K, V> FunctionalListenerAssertions<K, V> create( ConcurrentMap<K, V> map, Runnable r) { return new FunctionalListenerAssertions<>((FunctionalListeners<K, V>) map, r); } @SuppressWarnings("unchecked") public static <K, V> FunctionalListenerAssertions<K, V> create( Cache<K, V> map, Runnable r) { return new FunctionalListenerAssertions<>((FunctionalListeners<K, V>) map, r); } public static <K, V> FunctionalListenerAssertions<K, V> create( FunctionalListeners<K, V> listeners, Runnable r) { return new FunctionalListenerAssertions<>(listeners, r); } private FunctionalListenerAssertions(FunctionalListeners<K, V> listeners, Runnable runnable) { this.listeners = listeners; this.runnable = runnable; Listeners.ReadWriteListeners<K, V> rw = this.listeners.readWriteListeners(); closeables.add(rw.onCreate(c -> recorded.add( TestEvent.create(InternalTestType.LAMBDA_CREATE, c.get())))); closeables.add(rw.onModify((b, a) -> recorded.add( TestEvent.create(InternalTestType.LAMBDA_MODIFY, a.get(), b.get())))); closeables.add(rw.onRemove(r -> recorded.add( TestEvent.create(InternalTestType.LAMBDA_REMOVE, null, r.get())))); closeables.add(rw.add(new Listeners.ReadWriteListeners.ReadWriteListener<K, V>() { @Override public void onCreate(ReadEntryView<K, V> created) { recorded.add(TestEvent.create(InternalTestType.LISTENER_CREATE, created.get())); } @Override public void onModify(ReadEntryView<K, V> before, ReadEntryView<K, V> after) { recorded.add(TestEvent.create(InternalTestType.LISTENER_MODIFY, after.get(), before.get())); } @Override public void onRemove(ReadEntryView<K, V> removed) { recorded.add(TestEvent.create(InternalTestType.LISTENER_REMOVE, null, removed.get())); } })); Listeners.WriteListeners<K, V> wo = this.listeners.writeOnlyListeners(); closeables.add(wo.onWrite(w -> recorded.add( TestEvent.create(InternalTestType.LAMBDA_WRITE, w.find().orElse(null))))); closeables.add(wo.add(w -> recorded.add( TestEvent.create(InternalTestType.LISTENER_WRITE, w.find().orElse(null))))); } @Override public void close() { closeables.forEach(ac -> { try { ac.close(); } catch (Exception e) { throw new AssertionError(e); } }); } public void assertOrderedEvents(Collection<TestEvent<V>> expected) { runnable.run(); assertEquals(expected, recorded); } public void assertUnorderedEvents(Collection<TestEvent<V>> expected) { runnable.run(); assertEquals(recorded.toString(), expected.size(), recorded.size()); expected.forEach(e -> assertTrue(String.format("Value %s not in %s", e, recorded), recorded.remove(e))); assertEquals(0, recorded.size()); } private static <K, V> void withAssertions(ConcurrentMap<K, V> map, Runnable r, Consumer<FunctionalListenerAssertions<K, V>> c) { try(FunctionalListenerAssertions<K, V> a = FunctionalListenerAssertions.create(map, r)) { c.accept(a); } } private static <K, V> void withAssertions(Cache<K, V> map, Runnable r, Consumer<FunctionalListenerAssertions<K, V>> c) { try(FunctionalListenerAssertions<K, V> a = FunctionalListenerAssertions.create(map, r)) { c.accept(a); } } private static <K, V> void withAssertions(FunctionalListeners<K, V> listeners, Runnable r, Consumer<FunctionalListenerAssertions<K, V>> c) { try(FunctionalListenerAssertions<K, V> a = FunctionalListenerAssertions.create(listeners, r)) { c.accept(a); } } public static <K, V> void assertOrderedEvents(ConcurrentMap<K, V> map, Runnable r, Collection<TestEvent<V>> expected) { withAssertions(map, r, a -> a.assertOrderedEvents(expected)); } public static <K, V> void assertOrderedEvents(Cache<K, V> cache, Runnable r, Collection<TestEvent<V>> expected) { withAssertions(cache, r, a -> a.assertOrderedEvents(expected)); } public static <K, V> void assertOrderedEvents(FunctionalListeners<K, V> listeners, Runnable r, Collection<TestEvent<V>> expected) { withAssertions(listeners, r, a -> a.assertOrderedEvents(expected)); } public static <K, V> void assertUnorderedEvents(ConcurrentMap<K, V> map, Runnable r, Collection<TestEvent<V>> expected) { withAssertions(map, r, a -> a.assertUnorderedEvents(expected)); } public static <K, V> void assertUnorderedEvents(Cache<K, V> cache, Runnable r, Collection<TestEvent<V>> expected) { withAssertions(cache, r, a -> a.assertUnorderedEvents(expected)); } public static <K, V> void assertUnorderedEvents(FunctionalListeners<K, V> listeners, Runnable r, Collection<TestEvent<V>> expected) { withAssertions(listeners, r, a -> a.assertUnorderedEvents(expected)); } public static <K, V> void assertNoEvents(ConcurrentMap<K, V> map, Runnable r) { withAssertions(map, r, a -> a.assertOrderedEvents(new ArrayList<>())); } public static <K, V> void assertNoEvents(Cache<K, V> cache, Runnable r) { withAssertions(cache, r, a -> a.assertOrderedEvents(new ArrayList<>())); } public static <K, V> void assertNoEvents(FunctionalListeners<K, V> listeners, Runnable r) { withAssertions(listeners, r, a -> a.assertOrderedEvents(new ArrayList<>())); } public static Collection<TestEvent<String>> create(String... values) { List<TestEvent<String>> all = new ArrayList<>(); for (String value : values) all.addAll(TestEvent.create(CREATE, value)); for (String value : values) all.addAll(TestEvent.create(WRITE, value)); return all; } public static Collection<TestEvent<String>> createModify(String createdValue, String modifiedValue) { List<TestEvent<String>> all = new ArrayList<>(); all.addAll(TestEvent.create(CREATE, createdValue)); all.addAll(TestEvent.create(WRITE, createdValue)); all.addAll(TestEvent.create(MODIFY, modifiedValue, createdValue)); all.addAll(TestEvent.create(WRITE, modifiedValue)); return all; } public static Collection<TestEvent<String>> createRemove(String value) { List<TestEvent<String>> all = new ArrayList<>(); all.addAll(TestEvent.create(CREATE, value)); all.addAll(TestEvent.create(WRITE, value)); all.addAll(TestEvent.create(REMOVE, null, value)); all.addAll(TestEvent.create(WRITE, null)); return all; } public static Collection<TestEvent<String>> createAllRemoveAll(String... values) { List<TestEvent<String>> all = new ArrayList<>(); for (String s : values) { all.addAll(TestEvent.create(CREATE, s)); all.addAll(TestEvent.create(WRITE, s)); } for (String s : values) { all.addAll(TestEvent.create(REMOVE, null, s)); all.addAll(TestEvent.create(WRITE, null)); } return all; } public static Collection<TestEvent<String>> createThenRemove(String... values) { List<TestEvent<String>> all = new ArrayList<>(); for (String s : values) { all.addAll(TestEvent.create(CREATE, s)); all.addAll(TestEvent.create(WRITE, s)); all.addAll(TestEvent.create(REMOVE, null, s)); all.addAll(TestEvent.create(WRITE, null)); } return all; } public static Collection<TestEvent<String>> write(String... values) { List<TestEvent<String>> all = new ArrayList<>(); for (String value : values) all.addAll(TestEvent.create(WRITE, value)); return all; } public static Collection<TestEvent<String>> writeModify(List<String> written, List<String> modified) { List<TestEvent<String>> all = new ArrayList<>(); for (String value : written) all.addAll(TestEvent.create(WRITE, value)); IntStream.range(0, modified.size()).forEach(i -> { all.addAll(TestEvent.create(MODIFY, modified.get(i), written.get(i))); all.addAll(TestEvent.create(WRITE, modified.get(i))); } ); return all; } public static Collection<TestEvent<String>> createModifyRemove(String created, String modified) { List<TestEvent<String>> all = new ArrayList<>(); all.addAll(TestEvent.create(CREATE, created)); all.addAll(TestEvent.create(WRITE, created)); all.addAll(TestEvent.create(MODIFY, modified, created)); all.addAll(TestEvent.create(WRITE, modified)); all.addAll(TestEvent.create(REMOVE, null, modified)); all.addAll(TestEvent.create(WRITE, null)); return all; } public static Collection<TestEvent<String>> createModifyRemove(List<String> created, List<String> modified) { List<TestEvent<String>> all = new ArrayList<>(); created.forEach(s -> all.addAll(TestEvent.create(CREATE, s))); created.forEach(s -> all.addAll(TestEvent.create(WRITE, s))); modified.forEach(s -> all.addAll(TestEvent.create(WRITE, s))); modified.forEach(s -> all.addAll(TestEvent.create(REMOVE, null, s))); modified.forEach(s -> all.addAll(TestEvent.create(WRITE, null))); return all; } public static Collection<TestEvent<String>> writeValueNull(String... values) { List<TestEvent<String>> all = new ArrayList<>(); for (String s : values) all.addAll(TestEvent.create(WRITE, s)); for (String s : values) all.addAll(TestEvent.create(WRITE, null)); return all; } public static Collection<TestEvent<String>> writeRemove(String... values) { List<TestEvent<String>> all = new ArrayList<>(); for (String s : values) all.addAll(TestEvent.create(WRITE, s)); for (String s : values) all.addAll(TestEvent.create(REMOVE, null, s)); for (String s : values) all.addAll(TestEvent.create(WRITE, null)); return all; } public static final class TestEvent<V> { final InternalTestType type; final Optional<V> prev; final V value; public static <V> Collection<TestEvent<V>> create(TestType type, V value) { return Arrays.asList(TestEvent.create(type.lambdaType(), value), TestEvent.create(type.listenerType(), value)); } public static <V> Collection<TestEvent<V>> create(TestType type, V value, V prev) { return Arrays.asList(TestEvent.create(type.lambdaType(), value, prev), TestEvent.create(type.listenerType(), value, prev)); } private static <V> TestEvent<V> create(InternalTestType type, V value) { return new TestEvent<>(type, Optional.empty(), value); } private static <V> TestEvent<V> create(InternalTestType type, V value, V prev) { return new TestEvent<>(type, Optional.of(prev), value); } private TestEvent(InternalTestType type, Optional<V> prev, V value) { this.type = type; this.prev = prev; this.value = value; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TestEvent<?> testEvent = (TestEvent<?>) o; if (type != testEvent.type) return false; if (!prev.equals(testEvent.prev)) return false; return !(value != null ? !value.equals(testEvent.value) : testEvent.value != null); } @Override public int hashCode() { int result = type.hashCode(); result = 31 * result + prev.hashCode(); result = 31 * result + (value != null ? value.hashCode() : 0); return result; } @Override public String toString() { return "TestEvent{" + "type=" + type + ", prev=" + prev + ", value=" + value + '}'; } } }