/* * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.yangtools.util.concurrent; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import com.google.common.base.Stopwatch; import com.google.common.util.concurrent.Uninterruptibles; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Unit tests for QueuedNotificationManager. * * @author Thomas Pantelis */ public class QueuedNotificationManagerTest { static class TestListener<N> { private final List<N> actual; private volatile int expCount; private volatile CountDownLatch latch; volatile long sleepTime = 0; volatile RuntimeException runtimeEx; volatile Error jvmError; boolean cacheNotifications = true; String name; TestListener(final int expCount, final int id) { name = "TestListener " + id; actual = Collections.synchronizedList(new ArrayList<>(expCount)); reset(expCount); } void reset(final int expCount) { this.expCount = expCount; latch = new CountDownLatch(expCount); actual.clear(); } void onNotification(final N data) { try { if (sleepTime > 0) { Uninterruptibles.sleepUninterruptibly( sleepTime, TimeUnit.MILLISECONDS); } if (cacheNotifications) { actual.add(data); } RuntimeException localRuntimeEx = runtimeEx; if (localRuntimeEx != null) { runtimeEx = null; throw localRuntimeEx; } Error localJvmError = jvmError; if (localJvmError != null) { jvmError = null; throw localJvmError; } } finally { latch.countDown(); } } void verifyNotifications() { boolean done = Uninterruptibles.awaitUninterruptibly(latch, 10, TimeUnit.SECONDS); if (!done) { long actualCount = latch.getCount(); fail(name + ": Received " + (expCount - actualCount) + " notifications. Expected " + expCount); } } void verifyNotifications(final List<N> expected) { verifyNotifications(); assertEquals(name + ": Notifications", expected, actual); } // Implement bad hashCode/equals methods to verify it doesn't screw up the // QueuedNotificationManager as it should use reference identity. @Override public int hashCode() { return 1; } @Override public boolean equals(final Object obj) { TestListener<?> other = (TestListener<?>) obj; return other != null; } } static class TestListener2<N> extends TestListener<N> { TestListener2(final int expCount, final int id) { super(expCount, id); } } static class TestListener3<N> extends TestListener<N> { TestListener3(final int expCount, final int id) { super(expCount, id); } } static class TestNotifier<N> implements QueuedNotificationManager.Invoker<TestListener<N>, N> { @Override public void invokeListener(final TestListener<N> listener, final N notification) { listener.onNotification(notification); } } private static final Logger LOG = LoggerFactory.getLogger(QueuedNotificationManagerTest.class); private ExecutorService queueExecutor; @After public void tearDown() { if (queueExecutor != null) { queueExecutor.shutdownNow(); } } @Test(timeout=10000) public void testNotificationsWithSingleListener() { queueExecutor = Executors.newFixedThreadPool( 2 ); NotificationManager<TestListener<Integer>, Integer> manager = new QueuedNotificationManager<>(queueExecutor, new TestNotifier<>(), 10, "TestMgr" ); int initialCount = 6; int nNotifications = 100; TestListener<Integer> listener = new TestListener<>(nNotifications, 1); listener.sleepTime = 20; manager.submitNotifications(listener, Arrays.asList(1, 2)); manager.submitNotification(listener, 3); manager.submitNotifications(listener, Arrays.asList(4, 5)); manager.submitNotification(listener, 6); manager.submitNotifications(null, Collections.emptyList()); manager.submitNotifications(listener, null); manager.submitNotification(listener, null); Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); listener.sleepTime = 0; List<Integer> expNotifications = new ArrayList<>(nNotifications); expNotifications.addAll(Arrays.asList(1, 2, 3, 4, 5, 6)); for (int i = 1; i <= nNotifications - initialCount; i++) { Integer v = Integer.valueOf(initialCount + i); expNotifications.add(v); manager.submitNotification(listener, v); } listener.verifyNotifications( expNotifications ); } @Test public void testNotificationsWithMultipleListeners() throws InterruptedException { int nListeners = 10; queueExecutor = Executors.newFixedThreadPool(nListeners); final ExecutorService stagingExecutor = Executors.newFixedThreadPool(nListeners); final NotificationManager<TestListener<Integer>, Integer> manager = new QueuedNotificationManager<>( queueExecutor, new TestNotifier<>(), 5000, "TestMgr" ); final int nNotifications = 100000; LOG.info("Testing {} listeners with {} notifications each...", nListeners, nNotifications); final Integer[] notifications = new Integer[nNotifications]; for (int i = 1; i <= nNotifications; i++) { notifications[i - 1] = Integer.valueOf(i); } Stopwatch stopWatch = Stopwatch.createStarted(); List<TestListener<Integer>> listeners = new ArrayList<>(); List<Thread> threads = new ArrayList<>(); for (int i = 1; i <= nListeners; i++) { final TestListener<Integer> listener = i == 2 ? new TestListener2<>(nNotifications, i) : i == 3 ? new TestListener3<>(nNotifications, i) : new TestListener<>(nNotifications, i); listeners.add(listener); final Thread t = new Thread(() -> { for (int j = 1; j <= nNotifications; j++) { final Integer n = notifications[j - 1]; stagingExecutor.execute(() -> manager.submitNotification(listener, n)); } }); t.start(); threads.add(t); } try { for (TestListener<Integer> listener: listeners) { listener.verifyNotifications(); LOG.info("{} succeeded", listener.name); } } finally { stagingExecutor.shutdownNow(); } stopWatch.stop(); LOG.info("Elapsed time: {}", stopWatch); LOG.info("Executor: {}", queueExecutor); for (Thread t : threads) { t.join(); } } @Test(timeout=10000) public void testNotificationsWithListenerRuntimeEx() { queueExecutor = Executors.newFixedThreadPool(1); NotificationManager<TestListener<Integer>, Integer> manager = new QueuedNotificationManager<>( queueExecutor, new TestNotifier<>(), 10, "TestMgr" ); TestListener<Integer> listener = new TestListener<>(2, 1); final RuntimeException mockedRuntimeException = mock(RuntimeException.class); doNothing().when(mockedRuntimeException).printStackTrace(any(PrintStream.class)); listener.runtimeEx = mockedRuntimeException; manager.submitNotification(listener, 1); manager.submitNotification(listener, 2); listener.verifyNotifications(); } @Test(timeout=10000) public void testNotificationsWithListenerJVMError() { final CountDownLatch errorCaughtLatch = new CountDownLatch(1); queueExecutor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>()) { @Override public void execute(final Runnable command) { super.execute(() -> { try { command.run(); } catch (Error e) { errorCaughtLatch.countDown(); } }); } }; NotificationManager<TestListener<Integer>, Integer> manager = new QueuedNotificationManager<>(queueExecutor, new TestNotifier<>(), 10, "TestMgr"); TestListener<Integer> listener = new TestListener<>(2, 1); listener.jvmError = mock(Error.class); manager.submitNotification(listener, 1); assertEquals("JVM Error caught", true, Uninterruptibles.awaitUninterruptibly( errorCaughtLatch, 5, TimeUnit.SECONDS)); manager.submitNotification(listener, 2); listener.verifyNotifications(); } }