/* * Copyright 2015 Ben Manes. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.benmanes.caffeine.jcache.event; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import javax.cache.Cache; import javax.cache.configuration.MutableCacheEntryListenerConfiguration; import javax.cache.event.CacheEntryCreatedListener; import javax.cache.event.CacheEntryEventFilter; import javax.cache.event.CacheEntryExpiredListener; import javax.cache.event.CacheEntryListener; import javax.cache.event.CacheEntryRemovedListener; import javax.cache.event.CacheEntryUpdatedListener; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.common.util.concurrent.MoreExecutors; /** * @author ben.manes@gmail.com (Ben Manes) */ public final class EventDispatcherTest { @Mock Cache<Integer, Integer> cache; @Mock CacheEntryCreatedListener<Integer, Integer> createdListener; @Mock CacheEntryUpdatedListener<Integer, Integer> updatedListener; @Mock CacheEntryRemovedListener<Integer, Integer> removedListener; @Mock CacheEntryExpiredListener<Integer, Integer> expiredListener; CacheEntryEventFilter<Integer, Integer> allowFilter = event -> true; CacheEntryEventFilter<Integer, Integer> rejectFilter = event -> false; EventDispatcher<Integer, Integer> dispatcher; @BeforeMethod public void before() { MockitoAnnotations.initMocks(this); dispatcher = new EventDispatcher<>(MoreExecutors.directExecutor()); } @AfterMethod public void after() { dispatcher.ignoreSynchronous(); } @Test public void register_noListener() { MutableCacheEntryListenerConfiguration<Integer, Integer> configuration = new MutableCacheEntryListenerConfiguration<>(null, null, false, false); dispatcher.register(configuration); assertThat(dispatcher.dispatchQueues.keySet(), is(empty())); } @Test public void deregister() { MutableCacheEntryListenerConfiguration<Integer, Integer> configuration = new MutableCacheEntryListenerConfiguration<>(() -> createdListener, null, false, false); dispatcher.register(configuration); dispatcher.deregister(configuration); assertThat(dispatcher.dispatchQueues.keySet(), is(empty())); } @Test public void publishCreated() { registerAll(); dispatcher.publishCreated(cache, 1, 2); verify(createdListener, times(4)).onCreated(any()); assertThat(EventDispatcher.pending.get(), hasSize(2)); } @Test public void publishUpdated() { registerAll(); dispatcher.publishUpdated(cache, 1, 2, 3); verify(updatedListener, times(4)).onUpdated(any()); assertThat(EventDispatcher.pending.get(), hasSize(2)); } @Test public void publishRemoved() { registerAll(); dispatcher.publishRemoved(cache, 1, 2); verify(removedListener, times(4)).onRemoved(any()); assertThat(EventDispatcher.pending.get(), hasSize(2)); } @Test public void publishExpired() { registerAll(); dispatcher.publishExpired(cache, 1, 2); verify(expiredListener, times(4)).onExpired(any()); assertThat(EventDispatcher.pending.get(), hasSize(2)); } @Test(threadPoolSize = 5, invocationCount = 25) public void concurrent() { dispatcher.publishCreated(cache, 1, 2); dispatcher.publishUpdated(cache, 1, 2, 3); dispatcher.publishRemoved(cache, 1, 2); dispatcher.publishExpired(cache, 1, 2); } @Test public void awaitSynchronous() { EventDispatcher.pending.get().add(CompletableFuture.completedFuture(null)); dispatcher.awaitSynchronous(); assertThat(EventDispatcher.pending.get(), is(empty())); } @Test public void awaitSynchronous_failure() { CompletableFuture<Void> future = new CompletableFuture<>(); future.completeExceptionally(new RuntimeException()); EventDispatcher.pending.get().add(future); dispatcher.awaitSynchronous(); assertThat(EventDispatcher.pending.get(), is(empty())); } @Test public void ignoreSynchronous() { EventDispatcher.pending.get().add(CompletableFuture.completedFuture(null)); dispatcher.ignoreSynchronous(); assertThat(EventDispatcher.pending.get(), is(empty())); } /** * Registers (4 listeners) * (2 synchronous modes) * (3 filter modes) = 24 configurations. For * simplicity, an event is published and ignored if the listener is of the wrong type. For a * single event, it should be consumed by (2 filter) * (2 synchronous) = 4 listeners where only * 2 are synchronous. */ private void registerAll() { List<CacheEntryListener<Integer, Integer>> listeners = Arrays.asList( createdListener, updatedListener, removedListener, expiredListener); for (CacheEntryListener<Integer, Integer> listener : listeners) { for (boolean synchronous : Arrays.asList(true, false)) { dispatcher.register(new MutableCacheEntryListenerConfiguration<>( () -> listener, null, false, synchronous)); dispatcher.register(new MutableCacheEntryListenerConfiguration<>( () -> listener, () -> allowFilter, false, synchronous)); dispatcher.register(new MutableCacheEntryListenerConfiguration<>( () -> listener, () -> rejectFilter, false, synchronous)); } } } }